Compare commits
74 Commits
zeichenfen
...
f0a3c65552
| Author | SHA1 | Date | |
|---|---|---|---|
| f0a3c65552 | |||
| 13cad69e1d | |||
| bd718ba27d | |||
| 1895378978 | |||
| 2f845bdcd9 | |||
| 788ed888e9 | |||
| ce3ffee4da | |||
| 74c85e0f61 | |||
| c7b2a520c4 | |||
| c5c046521b | |||
| 9834b9c389 | |||
| eaaca6b90f | |||
| 1275af55f3 | |||
| 632038030e | |||
| cec17f0d7c | |||
| b29532bf6e | |||
| d500c130ed | |||
| b4d390cd9b | |||
| 97ea610f34 | |||
| e2e1f24e3e | |||
| a09b956b48 | |||
| c92a4517b3 | |||
| 1260a38bb7 | |||
| 20772da813 | |||
| 8898d6e8ee | |||
| cb0ee9c842 | |||
| 3cf7871591 | |||
| c7e1eb11ed | |||
| 9fc58b05b6 | |||
| 8de3c41b9b | |||
| 15e47ceaa8 | |||
| 9f28786ab6 | |||
| 18b5c50016 | |||
| 03945e029a | |||
| 90e043e5f8 | |||
| 559459aef6 | |||
| 8d0bd2bc99 | |||
| b76d533739 | |||
| 807a13b725 | |||
| d4c5dbbb53 | |||
| 080db1f431 | |||
| 47827683e8 | |||
| ec30afd441 | |||
| d3bdbdbffb | |||
| 8cc7167d7e | |||
| 9e4271c304 | |||
| 4f13f5177d | |||
| 6321a7d421 | |||
| 135af10729 | |||
| 912f68c58f | |||
| 7f1d9012e9 | |||
| 60ed045986 | |||
| dc16608333 | |||
| 7b6398fe52 | |||
| 8f98ddc56d | |||
| 782ce33540 | |||
| 537527e525 | |||
| 8e93866b5e | |||
| fcb536ff96 | |||
| 70c607f2e8 | |||
| 6126ed3c15 | |||
| b0353c53a0 | |||
| c93a203ab9 | |||
| f1d32685b4 | |||
| 91842b511f | |||
| 52b480b46b | |||
| 4d2ade899d | |||
| dcdca893b7 | |||
| ebf0135486 | |||
| fea1083926 | |||
| 250d9d17d3 | |||
| 2a71243fc6 | |||
| 03d37222bf | |||
| 687d7d35b7 |
8
.gitignore
vendored
@@ -48,3 +48,11 @@ gradle-app.setting
|
|||||||
|
|
||||||
# Cache of project
|
# Cache of project
|
||||||
.gradletasknamecache
|
.gradletasknamecache
|
||||||
|
|
||||||
|
# Python mkdocs
|
||||||
|
.venv
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|||||||
28
CHANGELOG.md
@@ -6,6 +6,34 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## Added
|
||||||
|
- Dokumentation `EventDispatcher`.
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
- Die Methoden in `Validator` erwarten nun als zweiten Parameter den Namen des Parameters, der geprüft wird.
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- `Constants.choice(int...)` und `Constants.choice(double...)` wiederhergestellt.
|
||||||
|
|
||||||
|
## Removed
|
||||||
|
- `layers.Shape2DLayer` ist nur noch im Test-Paket verfügbar.
|
||||||
|
|
||||||
|
## Version 0.0.34
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `Faker`-Klasse zur Erzeugung von Fake-Daten hinzugefügt.
|
||||||
|
- Dokumentation unter [zeichenmaschine.xyz](https://zeichenmaschine.xyz) mit
|
||||||
|
[MkDocs](https://www.mkdocs.org) und [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/).
|
||||||
|
- Neue `image` methoden im `DrawingLayer`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `FilledShape` und `StrokedShape` durch `Fillable` und `Strokeable` Interfaces ersetzt.
|
||||||
|
- `Shape` erweitert nun `BasisDrawable` als abstrakte Grundlage.
|
||||||
|
- `io` Klassen nutzen nun mehr der `java.nio` Funktionen.
|
||||||
|
- Package-Struktur angepasst.
|
||||||
|
|
||||||
|
## Version 0.0.23
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- System für EventListener.
|
- System für EventListener.
|
||||||
- `AudioListener` und `AnimationListener` als erste Anwendungsfälle.
|
- `AudioListener` und `AnimationListener` als erste Anwendungsfälle.
|
||||||
|
|||||||
41
build.gradle
@@ -3,17 +3,13 @@ plugins {
|
|||||||
id 'java-library'
|
id 'java-library'
|
||||||
}
|
}
|
||||||
|
|
||||||
/*properties {
|
|
||||||
zmVersion {
|
|
||||||
major = 0;
|
|
||||||
minor = 0;
|
|
||||||
rev = 21;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
group 'schule.ngb'
|
group 'schule.ngb'
|
||||||
version '0.0.22-SNAPSHOT'
|
version '0.0.34-SNAPSHOT'
|
||||||
//version '{$zmVersion.major}.{$zmVersion.minor}.{$zmVersion.rev}-SNAPSHOT'
|
|
||||||
|
java {
|
||||||
|
withSourcesJar()
|
||||||
|
withJavadocJar()
|
||||||
|
}
|
||||||
|
|
||||||
compileJava {
|
compileJava {
|
||||||
options.release = 11
|
options.release = 11
|
||||||
@@ -37,15 +33,36 @@ dependencies {
|
|||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
javadoc {
|
||||||
|
options {
|
||||||
|
encoding = "UTF-8"
|
||||||
|
overview = "src/main/java/overview.html"
|
||||||
|
// title = "Die Zeichenmaschine"
|
||||||
|
|
||||||
|
// options.links 'https://docs.oracle.com/javase/8/docs/api/'
|
||||||
|
// options.links 'https://docs.oracle.com/javaee/7/api'
|
||||||
|
options.links 'https://docs.oracle.com/en/java/javase/11/docs/api'
|
||||||
|
}
|
||||||
|
options.addStringOption("charset", "UTF-8")
|
||||||
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('jarMP3SPI', Jar) {
|
tasks.register('jarMP3SPI', Jar) {
|
||||||
archiveClassifier = 'all'
|
archiveClassifier = 'mp3spi'
|
||||||
duplicatesStrategy = 'exclude'
|
duplicatesStrategy = 'exclude'
|
||||||
archivesBaseName = 'zeichenmaschine-mp3spi'
|
// archivesBaseName = 'zeichenmaschine-mp3spi'
|
||||||
from {
|
from {
|
||||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||||
}
|
}
|
||||||
|
with jar
|
||||||
|
}
|
||||||
|
|
||||||
|
task buildAll {
|
||||||
|
dependsOn 'jar'
|
||||||
|
dependsOn 'jarMP3SPI'
|
||||||
|
dependsOn 'sourcesJar'
|
||||||
|
dependsOn 'javadocJar'
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
docs/assets/icon_128.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/assets/icon_32.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
docs/assets/icon_512.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
docs/assets/icon_64.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
634
docs/assets/quickstart/AblaufMoleActive.excalidraw
Normal file
@@ -0,0 +1,634 @@
|
|||||||
|
{
|
||||||
|
"type": "excalidraw",
|
||||||
|
"version": 2,
|
||||||
|
"source": "https://excalidraw.com",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "rectangle",
|
||||||
|
"version": 152,
|
||||||
|
"versionNonce": 1288225375,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "fxk8rHocjpTteICJMa6n8",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 619.9963325816416,
|
||||||
|
"y": 186.9758066195945,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#fab005",
|
||||||
|
"width": 150,
|
||||||
|
"height": 46,
|
||||||
|
"seed": 1339263918,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"boundElements": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "JmEBjNProPAgJQZUvMGGa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eryKwAzIMcBMQP0Ybl1Mm",
|
||||||
|
"type": "arrow"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updated": 1670307372550,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "rectangle",
|
||||||
|
"version": 264,
|
||||||
|
"versionNonce": 1267158257,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "wZLPkORf755_2Vp0J6I0V",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 566.9963325816416,
|
||||||
|
"y": 289.9758066195945,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#fab005",
|
||||||
|
"width": 248,
|
||||||
|
"height": 50,
|
||||||
|
"seed": 359801906,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"boundElements": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "9TujIdwDvtinylO3z50y6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eryKwAzIMcBMQP0Ybl1Mm",
|
||||||
|
"type": "arrow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Zc9GOJ8DsIQYo4WaGvvHy",
|
||||||
|
"type": "arrow"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updated": 1670307379131,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "rectangle",
|
||||||
|
"version": 376,
|
||||||
|
"versionNonce": 1807861489,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "290mWFx31fA5smc5FqPUr",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 607.9963325816416,
|
||||||
|
"y": 392.9758066195945,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#228be6",
|
||||||
|
"width": 143,
|
||||||
|
"height": 50,
|
||||||
|
"seed": 1948766318,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"boundElements": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "_ISR-LCZm2Hu2G57R_uxN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Zc9GOJ8DsIQYo4WaGvvHy",
|
||||||
|
"type": "arrow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hUUqTCXva-vZjnBeM-PR3",
|
||||||
|
"type": "arrow"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updated": 1670307888001,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "rectangle",
|
||||||
|
"version": 518,
|
||||||
|
"versionNonce": 2136415391,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "kbEG_cCZadugfCPxYedhf",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 614.9963325816416,
|
||||||
|
"y": 589.9758066195944,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#228be6",
|
||||||
|
"width": 131,
|
||||||
|
"height": 50,
|
||||||
|
"seed": 97599794,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"boundElements": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "6KjVrNy_dxGXJtntdqrjY"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hUUqTCXva-vZjnBeM-PR3",
|
||||||
|
"type": "arrow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bplSSGA4kyy-Av7nKNK1B",
|
||||||
|
"type": "arrow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "qukSk_W6enSdwERPEPkhZ",
|
||||||
|
"type": "arrow"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updated": 1670307888001,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"version": 110,
|
||||||
|
"versionNonce": 1350901746,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "JmEBjNProPAgJQZUvMGGa",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 643.9963325816416,
|
||||||
|
"y": 199.9758066195945,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"width": 102,
|
||||||
|
"height": 20,
|
||||||
|
"seed": 1889147762,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"boundElements": [],
|
||||||
|
"updated": 1670164406970,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"text": "new Shapes()",
|
||||||
|
"baseline": 14,
|
||||||
|
"textAlign": "center",
|
||||||
|
"verticalAlign": "middle",
|
||||||
|
"containerId": "fxk8rHocjpTteICJMa6n8",
|
||||||
|
"originalText": "new Shapes()"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"version": 245,
|
||||||
|
"versionNonce": 1536101230,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "9TujIdwDvtinylO3z50y6",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 584.9963325816416,
|
||||||
|
"y": 304.9758066195945,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"width": 212,
|
||||||
|
"height": 20,
|
||||||
|
"seed": 1297543150,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"boundElements": [],
|
||||||
|
"updated": 1670164433291,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"text": "super(800, 800, \"Shapes\")",
|
||||||
|
"baseline": 14,
|
||||||
|
"textAlign": "center",
|
||||||
|
"verticalAlign": "middle",
|
||||||
|
"containerId": "wZLPkORf755_2Vp0J6I0V",
|
||||||
|
"originalText": "super(800, 800, \"Shapes\")"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"version": 362,
|
||||||
|
"versionNonce": 2069822514,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "_ISR-LCZm2Hu2G57R_uxN",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 650.4963325816416,
|
||||||
|
"y": 407.9758066195945,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"width": 58,
|
||||||
|
"height": 20,
|
||||||
|
"seed": 525219186,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"boundElements": [],
|
||||||
|
"updated": 1670164509204,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"text": "setup()",
|
||||||
|
"baseline": 14,
|
||||||
|
"textAlign": "center",
|
||||||
|
"verticalAlign": "middle",
|
||||||
|
"containerId": "290mWFx31fA5smc5FqPUr",
|
||||||
|
"originalText": "setup()"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"version": 502,
|
||||||
|
"versionNonce": 747611665,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "6KjVrNy_dxGXJtntdqrjY",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 655.4963325816416,
|
||||||
|
"y": 604.9758066195944,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"width": 50,
|
||||||
|
"height": 20,
|
||||||
|
"seed": 1808016110,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"boundElements": [],
|
||||||
|
"updated": 1670307245844,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"text": "draw()",
|
||||||
|
"baseline": 14,
|
||||||
|
"textAlign": "center",
|
||||||
|
"verticalAlign": "middle",
|
||||||
|
"containerId": "kbEG_cCZadugfCPxYedhf",
|
||||||
|
"originalText": "draw()"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "arrow",
|
||||||
|
"version": 31,
|
||||||
|
"versionNonce": 101778865,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "eryKwAzIMcBMQP0Ybl1Mm",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 694.9963325816416,
|
||||||
|
"y": 242.9758066195945,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#12b886",
|
||||||
|
"width": 0,
|
||||||
|
"height": 38,
|
||||||
|
"seed": 1130444978,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"boundElements": [],
|
||||||
|
"updated": 1670307364549,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"startBinding": {
|
||||||
|
"elementId": "fxk8rHocjpTteICJMa6n8",
|
||||||
|
"focus": 0,
|
||||||
|
"gap": 10
|
||||||
|
},
|
||||||
|
"endBinding": {
|
||||||
|
"elementId": "wZLPkORf755_2Vp0J6I0V",
|
||||||
|
"focus": 0.03225806451612903,
|
||||||
|
"gap": 9
|
||||||
|
},
|
||||||
|
"lastCommittedPoint": null,
|
||||||
|
"startArrowhead": null,
|
||||||
|
"endArrowhead": "arrow",
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
38
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "arrow",
|
||||||
|
"version": 160,
|
||||||
|
"versionNonce": 1672489439,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "Zc9GOJ8DsIQYo4WaGvvHy",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 686.7253597961584,
|
||||||
|
"y": 347.9758066195945,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#12b886",
|
||||||
|
"width": 0.3256260532492661,
|
||||||
|
"height": 36,
|
||||||
|
"seed": 1138446962,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"boundElements": [],
|
||||||
|
"updated": 1670307364549,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"startBinding": {
|
||||||
|
"elementId": "wZLPkORf755_2Vp0J6I0V",
|
||||||
|
"gap": 8,
|
||||||
|
"focus": 0.03595554587056003
|
||||||
|
},
|
||||||
|
"endBinding": {
|
||||||
|
"elementId": "290mWFx31fA5smc5FqPUr",
|
||||||
|
"gap": 9,
|
||||||
|
"focus": 0.09195903246894747
|
||||||
|
},
|
||||||
|
"lastCommittedPoint": null,
|
||||||
|
"startArrowhead": null,
|
||||||
|
"endArrowhead": "arrow",
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-0.3256260532492661,
|
||||||
|
36
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "arrow",
|
||||||
|
"version": 616,
|
||||||
|
"versionNonce": 447594705,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "hUUqTCXva-vZjnBeM-PR3",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 684.6524492354905,
|
||||||
|
"y": 452.97580661959455,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#228be6",
|
||||||
|
"width": 1.5815106831711319,
|
||||||
|
"height": 29.000000000000114,
|
||||||
|
"seed": 1169930546,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"boundElements": [],
|
||||||
|
"updated": 1670307888001,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"startBinding": {
|
||||||
|
"elementId": "290mWFx31fA5smc5FqPUr",
|
||||||
|
"focus": -0.044553538542618336,
|
||||||
|
"gap": 10
|
||||||
|
},
|
||||||
|
"endBinding": {
|
||||||
|
"elementId": "5cG3FjQlYuIGPZMsIMgJU",
|
||||||
|
"focus": 0.08947713014192164,
|
||||||
|
"gap": 6.999999999999915
|
||||||
|
},
|
||||||
|
"lastCommittedPoint": null,
|
||||||
|
"startArrowhead": null,
|
||||||
|
"endArrowhead": "arrow",
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1.5815106831711319,
|
||||||
|
29.000000000000114
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "rectangle",
|
||||||
|
"version": 560,
|
||||||
|
"versionNonce": 11022527,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "5cG3FjQlYuIGPZMsIMgJU",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 616.4963325816416,
|
||||||
|
"y": 488.9758066195945,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#228be6",
|
||||||
|
"width": 131,
|
||||||
|
"height": 50,
|
||||||
|
"seed": 609895569,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"boundElements": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "-Fl205ZvyGaxHAHt7C3r3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hUUqTCXva-vZjnBeM-PR3",
|
||||||
|
"type": "arrow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bplSSGA4kyy-Av7nKNK1B",
|
||||||
|
"type": "arrow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "qukSk_W6enSdwERPEPkhZ",
|
||||||
|
"type": "arrow"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updated": 1670307888001,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"version": 551,
|
||||||
|
"versionNonce": 685087263,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "-Fl205ZvyGaxHAHt7C3r3",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 647.4963325816416,
|
||||||
|
"y": 503.97580661959455,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"width": 69,
|
||||||
|
"height": 20,
|
||||||
|
"seed": 415680767,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670307260196,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"text": "update()",
|
||||||
|
"baseline": 14,
|
||||||
|
"textAlign": "center",
|
||||||
|
"verticalAlign": "middle",
|
||||||
|
"containerId": "5cG3FjQlYuIGPZMsIMgJU",
|
||||||
|
"originalText": "update()"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bplSSGA4kyy-Av7nKNK1B",
|
||||||
|
"type": "arrow",
|
||||||
|
"x": 683.9963325816416,
|
||||||
|
"y": 546.9758066195944,
|
||||||
|
"width": 1,
|
||||||
|
"height": 34,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#228be6",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"seed": 1937189809,
|
||||||
|
"version": 33,
|
||||||
|
"versionNonce": 1639939761,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670307888001,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-1,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"lastCommittedPoint": null,
|
||||||
|
"startBinding": {
|
||||||
|
"elementId": "5cG3FjQlYuIGPZMsIMgJU",
|
||||||
|
"focus": -0.044849023090586096,
|
||||||
|
"gap": 7.999999999999858
|
||||||
|
},
|
||||||
|
"endBinding": {
|
||||||
|
"elementId": "kbEG_cCZadugfCPxYedhf",
|
||||||
|
"focus": 0.022646536412078155,
|
||||||
|
"gap": 9
|
||||||
|
},
|
||||||
|
"startArrowhead": null,
|
||||||
|
"endArrowhead": "arrow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "qukSk_W6enSdwERPEPkhZ",
|
||||||
|
"type": "arrow",
|
||||||
|
"x": 758.9963325816416,
|
||||||
|
"y": 615.9758066195944,
|
||||||
|
"width": 87,
|
||||||
|
"height": 107.99999999999994,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"seed": 677436529,
|
||||||
|
"version": 170,
|
||||||
|
"versionNonce": 377207327,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": [],
|
||||||
|
"updated": 1670307315516,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
86,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83,
|
||||||
|
-106.99999999999994
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-1,
|
||||||
|
-107.99999999999994
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"lastCommittedPoint": null,
|
||||||
|
"startBinding": {
|
||||||
|
"elementId": "kbEG_cCZadugfCPxYedhf",
|
||||||
|
"focus": 0.04,
|
||||||
|
"gap": 13
|
||||||
|
},
|
||||||
|
"endBinding": {
|
||||||
|
"elementId": "5cG3FjQlYuIGPZMsIMgJU",
|
||||||
|
"focus": -0.26783652736088875,
|
||||||
|
"gap": 10.5
|
||||||
|
},
|
||||||
|
"startArrowhead": null,
|
||||||
|
"endArrowhead": "arrow"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"appState": {
|
||||||
|
"gridSize": null,
|
||||||
|
"viewBackgroundColor": "#ffffff"
|
||||||
|
},
|
||||||
|
"files": {}
|
||||||
|
}
|
||||||
BIN
docs/assets/quickstart/AblaufMoleActive.png
Normal file
|
After Width: | Height: | Size: 258 KiB |
1030
docs/assets/quickstart/AblaufMoleStatic.excalidraw
Normal file
BIN
docs/assets/quickstart/AblaufMoleStatic.png
Normal file
|
After Width: | Height: | Size: 411 KiB |
375
docs/assets/quickstart/CircleMouseCollision.excalidraw
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
{
|
||||||
|
"type": "excalidraw",
|
||||||
|
"version": 2,
|
||||||
|
"source": "https://excalidraw.com",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "Mhp-wQ2wZxCI4BYWvTvV2",
|
||||||
|
"type": "ellipse",
|
||||||
|
"x": 420,
|
||||||
|
"y": 233,
|
||||||
|
"width": 385.99999999999994,
|
||||||
|
"height": 385.99999999999994,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#ffdf22",
|
||||||
|
"fillStyle": "cross-hatch",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"seed": 474299186,
|
||||||
|
"version": 149,
|
||||||
|
"versionNonce": 2072559726,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670158627399,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "SqZRA75ACKHo0lB799wpS",
|
||||||
|
"type": "line",
|
||||||
|
"x": 605,
|
||||||
|
"y": 426,
|
||||||
|
"width": 173.02018127597637,
|
||||||
|
"height": 99.89324823492274,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#087f5b",
|
||||||
|
"backgroundColor": "#ffdf22",
|
||||||
|
"fillStyle": "cross-hatch",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "dashed",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"seed": 484805038,
|
||||||
|
"version": 286,
|
||||||
|
"versionNonce": 1603007154,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670159044301,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
173.02018127597637,
|
||||||
|
-99.89324823492274
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"lastCommittedPoint": null,
|
||||||
|
"startBinding": null,
|
||||||
|
"endBinding": null,
|
||||||
|
"startArrowhead": null,
|
||||||
|
"endArrowhead": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7XVC5Wqiy62pN9Vc92bgl",
|
||||||
|
"type": "text",
|
||||||
|
"x": 645.7358370304399,
|
||||||
|
"y": 356.14958623820286,
|
||||||
|
"width": 86,
|
||||||
|
"height": 20,
|
||||||
|
"angle": 5.766085793504818,
|
||||||
|
"strokeColor": "#087f5b",
|
||||||
|
"backgroundColor": "#ffdf22",
|
||||||
|
"fillStyle": "cross-hatch",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"seed": 1883808110,
|
||||||
|
"version": 228,
|
||||||
|
"versionNonce": 627525998,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670159044301,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"text": "moleRadius",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"textAlign": "left",
|
||||||
|
"verticalAlign": "top",
|
||||||
|
"baseline": 14,
|
||||||
|
"containerId": null,
|
||||||
|
"originalText": "moleRadius"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "zp9JMFpfOA6ZEWCCiqwW0",
|
||||||
|
"type": "line",
|
||||||
|
"x": 606,
|
||||||
|
"y": 427,
|
||||||
|
"width": 128.012747010704,
|
||||||
|
"height": 208.02230726873202,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#fa5252",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "dashed",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"seed": 224372978,
|
||||||
|
"version": 81,
|
||||||
|
"versionNonce": 563971630,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670158772553,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-128.012747010704,
|
||||||
|
-208.02230726873202
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"lastCommittedPoint": null,
|
||||||
|
"startBinding": null,
|
||||||
|
"endBinding": null,
|
||||||
|
"startArrowhead": null,
|
||||||
|
"endArrowhead": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "LIDFlKBUhkbZdzTUFT1qp",
|
||||||
|
"type": "line",
|
||||||
|
"x": 604.8240237554426,
|
||||||
|
"y": 425.10629955596494,
|
||||||
|
"width": 127.71248741178238,
|
||||||
|
"height": 4.628286654564533,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#fa5252",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "dashed",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"seed": 34525358,
|
||||||
|
"version": 124,
|
||||||
|
"versionNonce": 1131380590,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670158787752,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-127.71248741178238,
|
||||||
|
-4.628286654564533
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"lastCommittedPoint": null,
|
||||||
|
"startBinding": null,
|
||||||
|
"endBinding": null,
|
||||||
|
"startArrowhead": null,
|
||||||
|
"endArrowhead": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "-Of3RfHAvJDq0qHIwjm2U",
|
||||||
|
"type": "ellipse",
|
||||||
|
"x": 471.96813247323996,
|
||||||
|
"y": 413.60866976111646,
|
||||||
|
"width": 12,
|
||||||
|
"height": 12,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#4c6ef5",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"seed": 1050702514,
|
||||||
|
"version": 384,
|
||||||
|
"versionNonce": 1957432558,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670158970885,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "NB4WGe-lT7XqLrsKX8jN8",
|
||||||
|
"type": "ellipse",
|
||||||
|
"x": 472,
|
||||||
|
"y": 212,
|
||||||
|
"width": 12,
|
||||||
|
"height": 12,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#fa5252",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"seed": 2059751730,
|
||||||
|
"version": 277,
|
||||||
|
"versionNonce": 756441010,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670158769939,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T1vEWEHV-1FM4A7Q7dTzy",
|
||||||
|
"type": "ellipse",
|
||||||
|
"x": 601.6117120882632,
|
||||||
|
"y": 422.28007605476614,
|
||||||
|
"width": 6.436664554034337,
|
||||||
|
"height": 6.436664554034337,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#000000",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 0,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"seed": 1983845742,
|
||||||
|
"version": 376,
|
||||||
|
"versionNonce": 1202556466,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670158984089,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eEMIGJ2Ag5HqWZLPmjXMM",
|
||||||
|
"type": "text",
|
||||||
|
"x": 551.4695946462369,
|
||||||
|
"y": 432.8553279772536,
|
||||||
|
"width": 108,
|
||||||
|
"height": 20,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#000000",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "dashed",
|
||||||
|
"roughness": 0,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"seed": 817794290,
|
||||||
|
"version": 97,
|
||||||
|
"versionNonce": 1438641266,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670158980419,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"text": "(moleX, moleY)",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"textAlign": "left",
|
||||||
|
"verticalAlign": "top",
|
||||||
|
"baseline": 14,
|
||||||
|
"containerId": null,
|
||||||
|
"originalText": "(moleX, moleY)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6cMmrU3lcJbub8sEKnVWe",
|
||||||
|
"type": "text",
|
||||||
|
"x": 409.80838527586974,
|
||||||
|
"y": 186.48331271464983,
|
||||||
|
"width": 135,
|
||||||
|
"height": 20,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#c92a2a",
|
||||||
|
"backgroundColor": "#000000",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "dashed",
|
||||||
|
"roughness": 0,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"seed": 399694958,
|
||||||
|
"version": 166,
|
||||||
|
"versionNonce": 1571185842,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670158939920,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"text": "(mouseX, mouseY)",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"textAlign": "left",
|
||||||
|
"verticalAlign": "top",
|
||||||
|
"baseline": 14,
|
||||||
|
"containerId": null,
|
||||||
|
"originalText": "(mouseX, mouseY)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0idmv_zLtEYyctHB4lSWV",
|
||||||
|
"type": "text",
|
||||||
|
"x": 406.7568976895149,
|
||||||
|
"y": 390.3911282833501,
|
||||||
|
"width": 135,
|
||||||
|
"height": 20,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#364fc7",
|
||||||
|
"backgroundColor": "#000000",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "dashed",
|
||||||
|
"roughness": 0,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "sharp",
|
||||||
|
"seed": 1008210350,
|
||||||
|
"version": 224,
|
||||||
|
"versionNonce": 968376110,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670158975919,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"text": "(mouseX, mouseY)",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"textAlign": "left",
|
||||||
|
"verticalAlign": "top",
|
||||||
|
"baseline": 14,
|
||||||
|
"containerId": null,
|
||||||
|
"originalText": "(mouseX, mouseY)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"appState": {
|
||||||
|
"gridSize": null,
|
||||||
|
"viewBackgroundColor": "#ffffff"
|
||||||
|
},
|
||||||
|
"files": {}
|
||||||
|
}
|
||||||
BIN
docs/assets/quickstart/CircleMouseCollision.png
Normal file
|
After Width: | Height: | Size: 821 KiB |
375
docs/assets/quickstart/Layers.excalidraw
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
{
|
||||||
|
"type": "excalidraw",
|
||||||
|
"version": 2,
|
||||||
|
"source": "https://excalidraw.com",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "HPGP8tbAPy0aGI3_MCE3D",
|
||||||
|
"type": "diamond",
|
||||||
|
"x": 491.42779164474496,
|
||||||
|
"y": 479.56091158550765,
|
||||||
|
"width": 432.03244941244884,
|
||||||
|
"height": 55.325188818463005,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#868e96",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"seed": 1103578030,
|
||||||
|
"version": 138,
|
||||||
|
"versionNonce": 1150893294,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670163691142,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vhIFJN3-91oJXV-RfFsBf",
|
||||||
|
"type": "diamond",
|
||||||
|
"x": 491.42779164474496,
|
||||||
|
"y": 435.8942449188411,
|
||||||
|
"width": 432.03244941244884,
|
||||||
|
"height": 55.325188818463005,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#fab005",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"seed": 618886834,
|
||||||
|
"version": 198,
|
||||||
|
"versionNonce": 1515704562,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670163691142,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "B2pM-4m_3Dk2w_6dY-hVJ",
|
||||||
|
"type": "diamond",
|
||||||
|
"x": 491.42779164474496,
|
||||||
|
"y": 392.2275782521744,
|
||||||
|
"width": 432.03244941244884,
|
||||||
|
"height": 55.325188818463005,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#228be6",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"seed": 844345582,
|
||||||
|
"version": 231,
|
||||||
|
"versionNonce": 1954508590,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670163691142,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Ge28XC9PqD26hknNaFXKP",
|
||||||
|
"type": "diamond",
|
||||||
|
"x": 491.42779164474496,
|
||||||
|
"y": 348.5609115855077,
|
||||||
|
"width": 432.03244941244884,
|
||||||
|
"height": 55.325188818463005,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#40c057",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"seed": 1326657902,
|
||||||
|
"version": 249,
|
||||||
|
"versionNonce": 685040306,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670163691142,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "PLU80s2TkyMEAOVgolwJx",
|
||||||
|
"type": "line",
|
||||||
|
"x": 506.9963325816415,
|
||||||
|
"y": 507.9758066195945,
|
||||||
|
"width": 0,
|
||||||
|
"height": 132.00000000000006,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#40c057",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "dotted",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"seed": 1246804466,
|
||||||
|
"version": 39,
|
||||||
|
"versionNonce": 627399022,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670163691142,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
-132.00000000000006
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"lastCommittedPoint": null,
|
||||||
|
"startBinding": null,
|
||||||
|
"endBinding": null,
|
||||||
|
"startArrowhead": null,
|
||||||
|
"endArrowhead": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "xDdJ5FnbMLIbN95FE3Iyc",
|
||||||
|
"type": "line",
|
||||||
|
"x": 910.9963325816416,
|
||||||
|
"y": 506.9758066195945,
|
||||||
|
"width": 0,
|
||||||
|
"height": 132,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#40c057",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "dotted",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"seed": 1034568050,
|
||||||
|
"version": 77,
|
||||||
|
"versionNonce": 1363786866,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670163691142,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
-132
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"lastCommittedPoint": null,
|
||||||
|
"startBinding": null,
|
||||||
|
"endBinding": null,
|
||||||
|
"startArrowhead": null,
|
||||||
|
"endArrowhead": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "clYu5Q9GzKbAfohBEsGwn",
|
||||||
|
"type": "line",
|
||||||
|
"x": 709.9963325816416,
|
||||||
|
"y": 532.9758066195944,
|
||||||
|
"width": 0,
|
||||||
|
"height": 129.99999999999994,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#000000",
|
||||||
|
"backgroundColor": "#40c057",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "dotted",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"seed": 1798035246,
|
||||||
|
"version": 42,
|
||||||
|
"versionNonce": 244579246,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670163691142,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
-129.99999999999994
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"lastCommittedPoint": null,
|
||||||
|
"startBinding": null,
|
||||||
|
"endBinding": null,
|
||||||
|
"startArrowhead": null,
|
||||||
|
"endArrowhead": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "v2nD7_bvWJYHqs82XDWO2",
|
||||||
|
"type": "text",
|
||||||
|
"x": 935.9963325816416,
|
||||||
|
"y": 497.2235059947391,
|
||||||
|
"width": 159,
|
||||||
|
"height": 20,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#343a40",
|
||||||
|
"backgroundColor": "#40c057",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "dotted",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"seed": 1323260978,
|
||||||
|
"version": 111,
|
||||||
|
"versionNonce": 372528622,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670163691142,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"text": "Ebene 0: ColorLayer",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"textAlign": "left",
|
||||||
|
"verticalAlign": "top",
|
||||||
|
"baseline": 14,
|
||||||
|
"containerId": null,
|
||||||
|
"originalText": "Ebene 0: ColorLayer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c6T2j8hozL3RZcNYGMgRl",
|
||||||
|
"type": "text",
|
||||||
|
"x": 935.9963325816416,
|
||||||
|
"y": 453.55683932807256,
|
||||||
|
"width": 172,
|
||||||
|
"height": 20,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#e67700",
|
||||||
|
"backgroundColor": "#40c057",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "dotted",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"seed": 1949746930,
|
||||||
|
"version": 111,
|
||||||
|
"versionNonce": 1448527858,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670163691142,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"text": "Ebene 1: DrawingLayer",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"textAlign": "left",
|
||||||
|
"verticalAlign": "top",
|
||||||
|
"baseline": 14,
|
||||||
|
"containerId": null,
|
||||||
|
"originalText": "Ebene 1: DrawingLayer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bAmGMg2i4Hvv7t_obXSKn",
|
||||||
|
"type": "text",
|
||||||
|
"x": 935.9963325816416,
|
||||||
|
"y": 409.8901726614059,
|
||||||
|
"width": 174,
|
||||||
|
"height": 20,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#1864ab",
|
||||||
|
"backgroundColor": "#40c057",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "dotted",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"seed": 706529710,
|
||||||
|
"version": 144,
|
||||||
|
"versionNonce": 1968138286,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670163691142,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"text": "Ebene 2: ShapesLayer",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"textAlign": "left",
|
||||||
|
"verticalAlign": "top",
|
||||||
|
"baseline": 14,
|
||||||
|
"containerId": null,
|
||||||
|
"originalText": "Ebene 2: ShapesLayer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9-QieQt-nVqGCe5D4r15j",
|
||||||
|
"type": "text",
|
||||||
|
"x": 935.9963325816416,
|
||||||
|
"y": 366.2235059947392,
|
||||||
|
"width": 65,
|
||||||
|
"height": 20,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#2b8a3e",
|
||||||
|
"backgroundColor": "#40c057",
|
||||||
|
"fillStyle": "hachure",
|
||||||
|
"strokeWidth": 1,
|
||||||
|
"strokeStyle": "dotted",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"strokeSharpness": "round",
|
||||||
|
"seed": 2100742126,
|
||||||
|
"version": 179,
|
||||||
|
"versionNonce": 289074606,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1670163697684,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"text": "Ebene 3",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"textAlign": "left",
|
||||||
|
"verticalAlign": "top",
|
||||||
|
"baseline": 14,
|
||||||
|
"containerId": null,
|
||||||
|
"originalText": "Ebene 3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"appState": {
|
||||||
|
"gridSize": null,
|
||||||
|
"viewBackgroundColor": "#ffffff"
|
||||||
|
},
|
||||||
|
"files": {}
|
||||||
|
}
|
||||||
BIN
docs/assets/quickstart/Layers.png
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
docs/assets/quickstart/shapes_2.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
docs/assets/quickstart/shapes_3.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
docs/assets/quickstart/shapes_4.1.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
docs/assets/quickstart/shapes_4.2.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
docs/assets/quickstart/shapes_4.3.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
docs/assets/quickstart/shapes_5.1.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
docs/assets/quickstart/shapes_5.3.gif
Normal file
|
After Width: | Height: | Size: 408 KiB |
BIN
docs/assets/quickstart/shapes_6.1.gif
Normal file
|
After Width: | Height: | Size: 207 KiB |
BIN
docs/assets/quickstart/shapes_6.2.png
Normal file
|
After Width: | Height: | Size: 457 KiB |
12
docs/assets/zmstyles.css
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
h1.title {
|
||||||
|
text-align: center;
|
||||||
|
color: #363636;
|
||||||
|
margin-bottom: .25rem;
|
||||||
|
}
|
||||||
|
h2.subtitle {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #4a4a4a;
|
||||||
|
margin-top: -.25rem;
|
||||||
|
margin-bottom: -1.25rem;
|
||||||
|
}
|
||||||
43
docs/einfuehrung.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
<figure markdown>
|
||||||
|
{ width=128 }
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<h1 class="title">Zeichenmaschine.xyz</h1>
|
||||||
|
<h2 class="subtitle">Eine kleine Java-Bibliothek für grafische Programmierung im
|
||||||
|
Informatikunterricht.</h2>
|
||||||
|
|
||||||
|
## Projektidee
|
||||||
|
|
||||||
|
Die **Zeichenmaschine** ist eine für den Informatikunterricht entwickelte Bibliothek,
|
||||||
|
die unter anderem an [Processing](https://processing.org/) angelehnt ist. Die
|
||||||
|
Bibliothek soll einige der üblichen Anfängerschwierigkeiten mit Java vereinfachen
|
||||||
|
und für Schülerinnen und Schüler im Unterricht nutzbar machen.
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
|
||||||
|
Das Projekt befindet sich noch in der Entwicklungsphase und auch wenn die
|
||||||
|
aktuelle Version schon funktionsfähig ist und einen Großteil der angestrebten
|
||||||
|
Funktionen enthält, ist noch keine stabile Version 1.0 erreicht. Vor allem
|
||||||
|
am Umfang und konsistenten Design der APIs gilt es noch zu arbeiten und es
|
||||||
|
können sich Änderungen ergeben.
|
||||||
|
|
||||||
|
Feedback und Vorschläge zu diesem Prozess (oder auch eine Beteiligung an der
|
||||||
|
Entwicklung) können sehr gerne über [Github](https://github.com/jneug) oder
|
||||||
|
[Mastodon](https://bildung.social/@ngb) an mich kommuniziert werden.
|
||||||
|
|
||||||
|
(Gleiches gilt für diese Webseite zum Projekt.)
|
||||||
|
|
||||||
|
## Dokumentation
|
||||||
|
* [Schnellstart](quickstart.md)
|
||||||
|
* [Installation](installation.md)
|
||||||
|
* {{ javadoc_link() }}
|
||||||
|
|
||||||
|
## Über die Zeichenmaschine
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
|
||||||
|
In der Zeichenmaschine werden bewusst nur englischsprachige Bezeichner für
|
||||||
|
Klassen, Methoden und Variablen verwendet. Ausnahme sind einzelne Klassen,
|
||||||
|
die im Zusammnehang mit dem Namen der Bibliothek stehen, wie die
|
||||||
|
Hauptklasse `Zeichenmaschine`.
|
||||||
7
docs/home_override/home.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block tabs %}
|
||||||
|
{{ super() }}
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
{% block footer %}{% endblock %}
|
||||||
46
docs/installation.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Installation
|
||||||
|
|
||||||
|
Um ein einfaches Projekt mit der **Zeichenmaschine** aufzusetzen ist nicht mehr
|
||||||
|
nötig, als
|
||||||
|
die [JAR-Datei der aktuellen Version](https://github.com/jneug/zeichenmaschine/release/latest)
|
||||||
|
herunterzuladen und dem *Classpath* des Projekts hinzuzufügen. Beschreibungen
|
||||||
|
für
|
||||||
|
verschiedene Entwicklungsumgebungen sind hier aufgelistet.
|
||||||
|
|
||||||
|
## Integration in Entwicklungsumgebungen
|
||||||
|
|
||||||
|
### BlueJ
|
||||||
|
|
||||||
|
[BlueJ](https://bluej.org) sucht an drei Orten nach Bibliotheken, die für ein
|
||||||
|
Projekt in den Classpath aufgenommen werden:
|
||||||
|
|
||||||
|
- Für ein einzelnes Projekt im Projektordner im Unterordner `+libs`.
|
||||||
|
- Im Reiter "Bibliotheken" der BlueJ-Einstellungen.
|
||||||
|
|
||||||
|
Hier können Programmbibliotheken hinzugefügt werden, die dann allen Projekten
|
||||||
|
zur Verfügung stehen.
|
||||||
|
|
||||||
|
- Für alle Projekte und alle Nutzer dieser BlueJ-Version im
|
||||||
|
Unterordner `userlib` des Programmordners.
|
||||||
|
|
||||||
|
Auf Windows-Systemen ist dieser im Order `lib` des Installationsordners von BlueJ zu finden.
|
||||||
|
|
||||||
|
Auf macos-Systemen muss via Rechtsklick auf die Programmdatei `BlueJ.app` über den Menüpunkt "Paketinhalt zeigen" in den Ordner `Contents/Resources/Java/` navigiert werden.
|
||||||
|
|
||||||
|
### VSCode / VSCodium
|
||||||
|
|
||||||
|
> Coming soon
|
||||||
|
|
||||||
|
### IntelliJ
|
||||||
|
|
||||||
|
> Coming soon
|
||||||
|
|
||||||
|
### Eclipse
|
||||||
|
|
||||||
|
> Coming soon
|
||||||
|
|
||||||
|
### NetBeans
|
||||||
|
|
||||||
|
> Coming soon
|
||||||
|
|
||||||
|
## Unterstützung für MP3
|
||||||
128
docs/macros.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import re
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
def define_env(env):
|
||||||
|
@env.macro
|
||||||
|
def javadoc(clazz: str = None, target: str = None) -> str:
|
||||||
|
if not "javadoc_url" in env.variables:
|
||||||
|
return clazz
|
||||||
|
|
||||||
|
if not clazz:
|
||||||
|
return f"{env.variables['javadoc_url'].rstrip('/')}/index.html"
|
||||||
|
else:
|
||||||
|
if "javadoc_default_package" in env.variables and not clazz.startswith(env.variables['javadoc_default_package']):
|
||||||
|
clazz = f"{env.variables['javadoc_default_package'].rstrip('.')}.{clazz}"
|
||||||
|
javadoc_url = env.variables["javadoc_url"].rstrip("/")
|
||||||
|
|
||||||
|
path = list()
|
||||||
|
name = list()
|
||||||
|
|
||||||
|
for p in clazz.split('.'):
|
||||||
|
if p[0].islower():
|
||||||
|
path.append(p)
|
||||||
|
else:
|
||||||
|
name.append(p)
|
||||||
|
|
||||||
|
path = '/'.join(path) + '/' + '.'.join(name) + ".html"
|
||||||
|
if target:
|
||||||
|
path = f"{path}#{target}"
|
||||||
|
return f"{javadoc_url}/{path}"
|
||||||
|
|
||||||
|
@env.macro
|
||||||
|
def jd(cl: str = None, t: str = None) -> str:
|
||||||
|
return javadoc(cl, t)
|
||||||
|
|
||||||
|
@env.macro
|
||||||
|
def javadoc_link(
|
||||||
|
clazz: str = None,
|
||||||
|
target: str = None,
|
||||||
|
strip_package: bool = True,
|
||||||
|
strip_clazz: bool = False,
|
||||||
|
strip_params: bool = True,
|
||||||
|
title: str = None
|
||||||
|
) -> str:
|
||||||
|
name = clazz or "Javadoc"
|
||||||
|
if strip_package:
|
||||||
|
if clazz and clazz.rfind(".") > -1:
|
||||||
|
name = clazz[clazz.rfind(".") + 1 :]
|
||||||
|
if target:
|
||||||
|
# _target = re.sub(r"([^(][^,]*?\.)*?([^)]+)", lambda m: m.group(2), target)
|
||||||
|
_target = target
|
||||||
|
|
||||||
|
if m := re.match(r'^(.+?)\((.*)\)$', _target):
|
||||||
|
if strip_params and m.group(2):
|
||||||
|
params = m.group(2).split(',')
|
||||||
|
for i, param in enumerate(params):
|
||||||
|
dot = param.rfind('.')
|
||||||
|
if dot >= 0:
|
||||||
|
params[i] = param[dot+1:].strip()
|
||||||
|
params = ", ".join(params)
|
||||||
|
_target = f'{m.group(1)}({params})'
|
||||||
|
|
||||||
|
if strip_clazz:
|
||||||
|
name = _target
|
||||||
|
else:
|
||||||
|
name = f"{name}.{_target}"
|
||||||
|
if title:
|
||||||
|
name = title
|
||||||
|
|
||||||
|
return f"[`{name}`]({javadoc(clazz, target)})"
|
||||||
|
|
||||||
|
@env.macro
|
||||||
|
def jdl(
|
||||||
|
cl: str = None,
|
||||||
|
t: str = None,
|
||||||
|
p: bool = False,
|
||||||
|
c: bool = True,
|
||||||
|
title: str = None
|
||||||
|
) -> str:
|
||||||
|
return javadoc_link(cl, t, strip_package=not p, strip_clazz=not c, strip_params=True, title=title)
|
||||||
|
|
||||||
|
@env.macro
|
||||||
|
def jdc(
|
||||||
|
cl: str,
|
||||||
|
p: bool = False
|
||||||
|
) -> str:
|
||||||
|
return javadoc_link(cl, strip_package=not p)
|
||||||
|
|
||||||
|
@env.macro
|
||||||
|
def jdm(
|
||||||
|
cl: str,
|
||||||
|
t: str,
|
||||||
|
p: bool = False,
|
||||||
|
c: bool = False
|
||||||
|
) -> str:
|
||||||
|
return javadoc_link(cl, t, strip_package=not p, strip_clazz=not c)
|
||||||
|
|
||||||
|
@env.macro
|
||||||
|
def javadoc_signature(
|
||||||
|
clazz: str = None,
|
||||||
|
member: str = None,
|
||||||
|
package: str = None,
|
||||||
|
params: List[str] = list(),
|
||||||
|
) -> str:
|
||||||
|
sig = clazz or ""
|
||||||
|
if clazz and package:
|
||||||
|
sig = f"{package}.{sig}"
|
||||||
|
if member:
|
||||||
|
sig = f"{sig}#{member}"
|
||||||
|
|
||||||
|
pparams = ",".join(params)
|
||||||
|
sig = f"{sig}({pparams})"
|
||||||
|
|
||||||
|
return sig
|
||||||
|
|
||||||
|
@env.macro
|
||||||
|
def jds(
|
||||||
|
cl: str = None,
|
||||||
|
m: str = None,
|
||||||
|
pkg: str = None,
|
||||||
|
params: List[str] = list(),
|
||||||
|
) -> str:
|
||||||
|
javadoc_signature(cl, m, pkg, params)
|
||||||
|
|
||||||
|
|
||||||
|
# schule/ngb/zm/Zeichenmaschine.html#setCursor(java.awt.Image,int,int)
|
||||||
|
# schule/ngb/zm/Zeichenmaschine.html#getLayer(java.lang.Class)
|
||||||
|
# schule/ngb/zm/DrawableLayer.html#add(schule.ngb.zm.Drawable...)
|
||||||
606
docs/quickstart.md
Normal file
@@ -0,0 +1,606 @@
|
|||||||
|
# Schnellstart mit der Zeichenmaschine
|
||||||
|
|
||||||
|
Um die **Zeichenmaschine** in einem Projekt zu nutzen ist nicht mehr nötig, als
|
||||||
|
die [JAR-Datei der aktuellen Version](https://github.com/jneug/zeichenmaschine/release/latest)
|
||||||
|
herunterzuladen und
|
||||||
|
dem [Classpath](https://www.delftstack.com/de/howto/java/java-classpath-/)
|
||||||
|
hinzuzufügen. Eine Beschreibung für verschiedene Entwicklungsumgebungen findet
|
||||||
|
sich im Abschnitt [Installation](installation.md).
|
||||||
|
|
||||||
|
## Die Basisklasse
|
||||||
|
|
||||||
|
Eine _Zeichenmaschine_ wird immer als Unterklasse von {{ javadoc_link("
|
||||||
|
schule.ngb.zm.Zeichenmaschine") }} erstellt.
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class Shapes extends Zeichenmaschine {
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Die gezeigte Klasse ist schon eine lauffähige Zeichenmaschine und kann gestartet
|
||||||
|
werden.
|
||||||
|
|
||||||
|
!!! note "main Methode"
|
||||||
|
|
||||||
|
Bei einigen Entwicklungsumgebungen muss noch eine `main` Methode erstellt
|
||||||
|
werden, um die Zeichenmaschine zu starten:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public static void main(String[] args) {
|
||||||
|
new Shapes();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Es öffnet sich ein Zeichenfenster in einer vordefinierten Größe. Um die
|
||||||
|
Abmessungen und den Titel des Fensters zu ändern, legen wir einen Konstruktor
|
||||||
|
an.
|
||||||
|
|
||||||
|
???+ example "Quelltext"
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class Shapes extends Zeichenmaschine {
|
||||||
|
|
||||||
|
public Shapes() {
|
||||||
|
super(800, 800, "Shapes");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Starten wir das Projekt, wird eine Zeichenfläche in der Größe 800-mal-800 Pixel
|
||||||
|
erstellt und in einem Fenster mit dem Titel „Shapes“ angezeigt.
|
||||||
|
|
||||||
|
<figure markdown>
|
||||||
|
{ width=400 }
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
### Formen zeichnen
|
||||||
|
|
||||||
|
Eine Zeichenmaschine hat verschiedene Möglichkeiten, Inhalte in das
|
||||||
|
Zeichenfenster zu zeichnen. Um ein einfaches statisches Bild zu erzeugen,
|
||||||
|
überschreiben wir die {{ jdl("schule.ngb.zm.Zeichenmaschine", "draw()",
|
||||||
|
c=False) }} Methode.
|
||||||
|
|
||||||
|
???+ example "Quelltext"
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class Shapes extends Zeichenmaschine {
|
||||||
|
|
||||||
|
public Shapes() {
|
||||||
|
super(800, 800, "Shapes");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw() {
|
||||||
|
background.setColor(BLUE);
|
||||||
|
|
||||||
|
drawing.setFillColor(255, 223, 34);
|
||||||
|
drawing.noStroke();
|
||||||
|
drawing.circle(400, 400, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Wir sehen einen gelben Kreis (ohne Konturlinie) auf einem blauen Hintergrund.
|
||||||
|
|
||||||
|
<figure markdown>
|
||||||
|
{ width=400 }
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
### Vorbereitung der Zeichenfläche
|
||||||
|
|
||||||
|
Im Beispiel oben setzen wir die Hintergrundfarbe auf Blau, die Füllfarbe auf
|
||||||
|
Gelb und deaktivieren die Konturlinie. Wenn diese Einstellungen für alle
|
||||||
|
Zeichenobjekte gleich bleiben, können wir sie statt in `draw()` auch in die {{
|
||||||
|
jdl('Zeichenmaschine', 'setup()', c=False) }} Methode schreiben. Diese bereitet
|
||||||
|
die Zeichenfläche vor dem ersten Zeichnen vor.
|
||||||
|
|
||||||
|
???+ example "Quelltext"
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class Shapes extends Zeichenmaschine {
|
||||||
|
|
||||||
|
public Shapes() {
|
||||||
|
super(800, 800, "Shapes");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
background.setColor(BLUE);
|
||||||
|
|
||||||
|
drawing.setFillColor(255, 223, 34);
|
||||||
|
drawing.noStroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw() {
|
||||||
|
for( int i = 0; i < 10; i++ ) {
|
||||||
|
drawing.circle(
|
||||||
|
random(0, canvasWidth),
|
||||||
|
random(0, canvasHeight),
|
||||||
|
random(50, 200)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Im Beispiel setzen wir nun die Grundeinstellungen in der `setup()` Methode. In
|
||||||
|
`draw()` werden zehn gelbe Kreise an Zufallskoordinaten gezeichnet.
|
||||||
|
|
||||||
|
<figure markdown>
|
||||||
|
{ width=400 }
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
!!! tip ""
|
||||||
|
|
||||||
|
Mit {{ jdm("Constants", "canvasWidth") }} und
|
||||||
|
{{ jdm("Constants", "canvasHeight") }} kannst du in der Zeichenmaschine
|
||||||
|
auf die aktuelle Größe der Zeichenfläche zugreifen.
|
||||||
|
{{ jdm("Constants", "random(int,int)") }} erzeugt eine Zufallszahl
|
||||||
|
innerhalb der angegebenen Grenzen.
|
||||||
|
|
||||||
|
## Interaktionen mit der Maus: Whack-a-mole
|
||||||
|
|
||||||
|
Mit der Zeichenmaschine lassen sich Interaktionen mit der Maus leicht umsetzen.
|
||||||
|
Wor wollen das Beispielprogramm zu einem
|
||||||
|
[Whac-A-Mole](https://de.wikipedia.org/wiki/Whac-A-Mole) Spiel erweitern.
|
||||||
|
|
||||||
|
Auf der Zeichenfläche wird nur noch ein gelber Kreis an einer zufälligen Stelle
|
||||||
|
angezeigt. Sobald die Spieler:in auf den Kreis klickt, soll dieser an eine neue
|
||||||
|
Position springen.
|
||||||
|
|
||||||
|
Damit wir den Kreis an eine neue Position springen lassen können, müssen wir
|
||||||
|
zufällige `x`- und `y`-Koordinaten generieren. Dazu erstellen wir zunächst zwei
|
||||||
|
_Objektvariablen_ für die Koordinaten, die in der `setup()` Methode mit
|
||||||
|
zufälligen Werte initialisiert werden. Diese benutzen wir, um die `draw
|
||||||
|
()` Methode anzupassen.
|
||||||
|
|
||||||
|
??? example "Quelltext"
|
||||||
|
|
||||||
|
```Java
|
||||||
|
public class Shapes extends Zeichenmaschine {
|
||||||
|
|
||||||
|
private int moleRadius = 20;
|
||||||
|
|
||||||
|
private int moleX;
|
||||||
|
|
||||||
|
private int moleY;
|
||||||
|
|
||||||
|
public Shapes() {
|
||||||
|
super(800, 800, "Shapes");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
background.setColor(BLUE);
|
||||||
|
|
||||||
|
drawing.setFillColor(255, 223, 34);
|
||||||
|
drawing.noStroke();
|
||||||
|
|
||||||
|
moleX = random(50, canvasWidth - 50);
|
||||||
|
moleY = random(50, canvasHeight - 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw() {
|
||||||
|
drawing.clear();
|
||||||
|
drawing.circle(moleX, moleY, moleRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<figure markdown>
|
||||||
|
{ width=600 }
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Als Nächstes prüfen wir bei jedem Mausklick, ob die Mauskoordinaten innerhalb
|
||||||
|
des gelben Kreises (des Maulwurfs) liegen. Die Mauskoordinaten sind jederzeit
|
||||||
|
über die Variablen `mouseX` und `mouseY` abrufbar. Um zu prüfen, ob diese
|
||||||
|
Koordinaten innerhalb des Kreises liegen, vergleichen wir den Abstand zwischen
|
||||||
|
Kreismittelpunkt `(moleX, moleY)` und den Mauskoordinaten
|
||||||
|
`(mouseX, mouseY)` mit dem Radius des Kreises (im Bild grün). Ist die Entfernung
|
||||||
|
kleiner als der Radius (blauer Kreis), wurde innerhalb des Kreises geklickt.
|
||||||
|
Sonst außerhalb (roter Kreis).
|
||||||
|
|
||||||
|
<figure markdown>
|
||||||
|
{ width=400 }
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Den Abstand vom Mittelpunkt zur Maus lässt sich mithilfe des Satzes des
|
||||||
|
Pythagoras leicht selber berechnen. Die Zeichenmaschine kann uns diese Arbeit
|
||||||
|
aber auch abnehmen und stellt eine Methode dafür bereit
|
||||||
|
({{ jdm("Constants", "distance(double,double,double,double)") }}).
|
||||||
|
|
||||||
|
Um auf einen Mausklick zu reagieren, ergänzen wir die
|
||||||
|
{{ jdm("Zeichenmaschine", "mouseClicked()") }} Methode:
|
||||||
|
|
||||||
|
```
|
||||||
|
@Override
|
||||||
|
public void mouseClicked() {
|
||||||
|
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
|
||||||
|
moleX = random(50, canvasWidth - 50);
|
||||||
|
moleY = random(50, canvasHeight - 50);
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
??? example "Quelltext"
|
||||||
|
|
||||||
|
```Java
|
||||||
|
public class Shapes extends Zeichenmaschine {
|
||||||
|
|
||||||
|
private int moleRadius = 20;
|
||||||
|
|
||||||
|
private int moleX;
|
||||||
|
|
||||||
|
private int moleY;
|
||||||
|
|
||||||
|
public Shapes() {
|
||||||
|
super(800, 800, "Shapes");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
background.setColor(BLUE);
|
||||||
|
|
||||||
|
drawing.setFillColor(255, 223, 34);
|
||||||
|
drawing.noStroke();
|
||||||
|
|
||||||
|
moleX = random(50, canvasWidth - 50);
|
||||||
|
moleY = random(50, canvasHeight - 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw() {
|
||||||
|
drawing.clear();
|
||||||
|
drawing.circle(moleX, moleY, moleRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseClicked() {
|
||||||
|
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
|
||||||
|
moleX = random(50, canvasWidth - 50);
|
||||||
|
moleY = random(50, canvasHeight - 50);
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
Der Aufruf von {{ jdm("Zeichenmaschine", "redraw()") }} zeichnet
|
||||||
|
die Zeichenfläche neu, indem die `draw()` Methode erneut aufgerufen wird.
|
||||||
|
Du solltest `draw()` niemals direkt aufrufen.
|
||||||
|
|
||||||
|
Nun springt der Kreis an eine andere Stelle, wenn er direkt mit der Maus
|
||||||
|
angeklickt wird.
|
||||||
|
|
||||||
|
<figure markdown>
|
||||||
|
{ width=400 }
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
## Ein paar Details zur Zeichenmaschine
|
||||||
|
|
||||||
|
Die _Zeichenmaschine_ wurde stark von der kreativen Programmierumgebung
|
||||||
|
[Processing](https://processing.org) inspiriert. Wenn Du Processing schon
|
||||||
|
kennst, dann werden Dir einige der Konzepte der _Zeichenmaschine_ schon bekannt
|
||||||
|
vorkommen.
|
||||||
|
|
||||||
|
### Farben
|
||||||
|
|
||||||
|
Farben können auf verschiedene Weisen angegeben werden. Unser Beispiel nutzt
|
||||||
|
bisher zwei Arten:
|
||||||
|
|
||||||
|
1. Die einfachste Möglichkeit sind die _Farbkonstanten_
|
||||||
|
wie {{ jdm('Constants', 'BLUE') }} oder {{ jdm('Constants', 'RED') }}. Im
|
||||||
|
Beispiel setzen wir den Hintergrund auf die Farbe `BLUE`.
|
||||||
|
2. Farben werden häufig im RGB-Farbraum definiert. Dazu wird jeweils der Rot-,
|
||||||
|
Grün- und Blauanteil der Farbe als Wert zwischen 0 und 255 angegeben. Im
|
||||||
|
Beispiel setzen wir die Farbe der Kreise auf `255, 223, 34`, also viel Rot
|
||||||
|
und Grün und nur ein wenig Blau.
|
||||||
|
|
||||||
|
### Ebenen
|
||||||
|
|
||||||
|
Die Zeichenfläche besteht aus einzelnen {{ jdl("Layer", title="Ebenen") }}, die
|
||||||
|
auf übereinander liegen. Bis auf die unterste Ebene sind die Ebenen zunächst
|
||||||
|
durchsichtig, wodurch die Zeichnungen unterer Ebenen durchscheinen.
|
||||||
|
|
||||||
|
<figure markdown>
|
||||||
|
{ width=600 }
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Eine _Zeichenmaschine_ besitzt zu Beginn drei Ebenen:
|
||||||
|
|
||||||
|
1. Die unterste Ebene ist ein {{ jdc("layers.ColorLayer") }}, die nur aus einer
|
||||||
|
Farbe (oder einem Farbverlauf) besteht und keine durchsichtigen Bereiche
|
||||||
|
besitzt. Im Beispiel setzen wir diese Ebene auf die Farbe `BLUE`.
|
||||||
|
2. Die nächste Ebene ist ein {{ jdc("layers.DrawingLayer") }}, auf die wir
|
||||||
|
unsere Formen zeichnen können. Die Ebene ist zunächst komplett durchsichtig.
|
||||||
|
3. Die oberste Ebene ist ein {{ jdc("layers.ShapesLayer") }}, die zur
|
||||||
|
Darstellung von Form-Objekten der Klasse {{ jdc("shapes.Shape") }} genutzt
|
||||||
|
werden kann.
|
||||||
|
|
||||||
|
Du kannst einer Zeichenfläche aber auch beliebige neue oder selbst programmierte
|
||||||
|
Ebenen hinzufügen.
|
||||||
|
|
||||||
|
### Ablauf
|
||||||
|
|
||||||
|
Die _Zeichenmaschine_ ruft nach dem Start die Methoden in einem festen Ablauf
|
||||||
|
auf.
|
||||||
|
|
||||||
|
<figure markdown>
|
||||||
|
{ width=500 }
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Erstellst Du eine _Zeichenmaschine_ (beziehungsweise ein Objekt einer
|
||||||
|
Unterklasse), dann wird zuerst die {{ jdm('Zeichenmaschine', 'setup()') }}
|
||||||
|
Methode ausgeführt. Danach folgt einmalig die
|
||||||
|
{{ jdm('Zeichenmaschine', 'draw()') }} Methode und dann endet das Hauptprogramm.
|
||||||
|
|
||||||
|
Die Eingaben der Maus werden in einem parallelen Ablauf (einem _Thread_)
|
||||||
|
abgefangen und daraufhin die {{ jdm('Zeichenmaschine', 'mouseClicked()') }}
|
||||||
|
Methode aufgerufen. In unserem Programm prüfen wir, ob mit der Maus
|
||||||
|
innerhalb des Kreises geklickt wurde und rufen dann
|
||||||
|
{{ jdm('Zeichenmaschine', 'redraw()') }} auf, woraufhin ein weiteres Mal
|
||||||
|
`draw()` ausgeführt wird.
|
||||||
|
|
||||||
|
In _Processing_ wird dies der "statische" Modus genannt, weil das Programm nur
|
||||||
|
einmal abläuft und dann stoppt. "Statisch" trifft es nicht ganz, da das Programm
|
||||||
|
ja zum Beispiel durch Mauseingaben auch verändert werden kann. Wichtig ist aber,
|
||||||
|
dass mit `redraw()` die Zeichenfläche manuell neu gezeichnet werden muss, damit
|
||||||
|
sich der Inhalt ändert.
|
||||||
|
|
||||||
|
## Dynamische Programme
|
||||||
|
|
||||||
|
Wir wollen unser kleines Spiel dynamischer machen, indem die Kreise nur drei
|
||||||
|
Sekunden angezeigt werden und dann von selbst an einen neuen Ort springen.
|
||||||
|
Schafft man es, den Kreis in dieser Zeit anzuklicken, bekommt man einen Punkt.
|
||||||
|
|
||||||
|
Als zusätzliche Herausforderung lassen wir jeden Kreis in den drei Sekunden
|
||||||
|
immer kleiner werden.
|
||||||
|
|
||||||
|
### Die Update-Draw-Schleifen
|
||||||
|
|
||||||
|
{ width=200 align=right }
|
||||||
|
|
||||||
|
Bisher hat die _Zeichenmaschine_ einmalig `draw()` aufgerufen und dann nur noch
|
||||||
|
auf Benutzereingaben mit der Maus reagiert. Nun wollen wir das gezeichnete Bild
|
||||||
|
aber laufend anpassen. Der Kreis soll schrumpfen und nach 3 Sekunden
|
||||||
|
verschwinden.
|
||||||
|
|
||||||
|
Dazu ergänzen wir ein {{ jdl('Zeichenmaschine', 'update(double)', c=False) }}
|
||||||
|
Methode in unserem Programm. Nun schaltet die _Zeichenmaschine_ in einen
|
||||||
|
dynamischen Modus und startet die _Update-Draw-Schleife_. Das beduetet, nach
|
||||||
|
Aufruf von `setup()` wird fortlaufend immer wieder zuerst `update()` und dann
|
||||||
|
`draw()` aufgerufen.
|
||||||
|
|
||||||
|
Jeder Aufruf der `draw()` Methode zeichnet nun die Zeichenfläche neu. Jedes
|
||||||
|
Bild, das gezeichnet wird (auch, wenn es genauso aussieht, wie das davor), nennt
|
||||||
|
man ein _Frame_. Von Videospielen kennst Du vielleicht schon den Begriff "
|
||||||
|
_Frames per second_" (Fps, dt. Bilder pro Sekunde). Er bedeutet, wie oft das
|
||||||
|
Spiel neue Frames zeichnet, oder in der _Zeichenmaschine_, wie oft `draw()` pro
|
||||||
|
Sekunde aufgerufen wird. Normalerweise passiert dies genau 60-mal pro Sekunde.
|
||||||
|
|
||||||
|
### Lebenszeit eines Kreises
|
||||||
|
|
||||||
|
Jeder Kreis soll drei Sekunden zu sehen sein. Daher fügen wir eine neue
|
||||||
|
Objektvariable namens `moleTime` ein, die zunächst auf drei steht. Da wir auch
|
||||||
|
Bruchteile von Skeunden abziehen wollen, wählen wir als Datentyp `double`:
|
||||||
|
|
||||||
|
```Java
|
||||||
|
private double moleTime=3.0;
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Parameter `delta`, der `update()` Methode ist der Zeitraum in Sekunden, seit
|
||||||
|
dem letzten Frame. Subtrahieren wir diesen Wert bei jedem Frame von `moleTime`,
|
||||||
|
wird der Wert immer kleiner. Dann müssen wir nur noch prüfen, ob er kleiner Null
|
||||||
|
ist und in dem Fall den Kreis auf eine neue Position springen lassen.
|
||||||
|
|
||||||
|
Die Größe des Kreises passen wir so an, dass der Anteil der vergangenen Zeit die
|
||||||
|
Größe des Kreises bestimmt:
|
||||||
|
|
||||||
|
```Java
|
||||||
|
drawing.circle(moleX,moleY,moleRadius*(moleTime/3.0));
|
||||||
|
```
|
||||||
|
|
||||||
|
??? example "Quelltext"
|
||||||
|
|
||||||
|
```Java
|
||||||
|
import schule.ngb.zm.Zeichenmaschine;
|
||||||
|
|
||||||
|
public class Shapes extends Zeichenmaschine {
|
||||||
|
|
||||||
|
private int moleRadius = 20;
|
||||||
|
|
||||||
|
private int moleX;
|
||||||
|
|
||||||
|
private int moleY;
|
||||||
|
|
||||||
|
private double moleTime;
|
||||||
|
|
||||||
|
public Shapes() {
|
||||||
|
super(800, 800, "Shapes");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
background.setColor(BLUE);
|
||||||
|
|
||||||
|
drawing.setFillColor(255, 223, 34);
|
||||||
|
drawing.noStroke();
|
||||||
|
|
||||||
|
randomizeMole();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void randomizeMole() {
|
||||||
|
moleX = random(moleRadius*2, canvasWidth - moleRadius*2);
|
||||||
|
moleY = random(moleRadius*2, canvasHeight - moleRadius*2);
|
||||||
|
moleTime = 3.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update( double delta ) {
|
||||||
|
moleTime -= delta;
|
||||||
|
|
||||||
|
if( moleTime <= 0 ) {
|
||||||
|
randomizeMole();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw() {
|
||||||
|
drawing.clear();
|
||||||
|
drawing.circle(moleX, moleY, moleRadius * (moleTime / 3.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseClicked() {
|
||||||
|
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
|
||||||
|
randomizeMole();
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main( String[] args ) {
|
||||||
|
new Shapes();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<figure markdown>
|
||||||
|
{ width=400 }
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
!!! tip ""
|
||||||
|
|
||||||
|
Der Maulwurf muss mittlerweile an drei verschiedenen Stellen im Programm
|
||||||
|
auf eine zufällige Position gesetzt werden (am Anfang, wenn er angeklickt
|
||||||
|
wurde und wenn die drei Sekunden abgelaufen sind). Daher wurde das Versetzen
|
||||||
|
in eine eigene Methode `randomizeMole()` ausgelagert.
|
||||||
|
|
||||||
|
### Punktezähler
|
||||||
|
|
||||||
|
Zum Schluss wollen wir noch bei jedem Treffer mit der Maus die Punkte Zählen und
|
||||||
|
als Text auf die Zeichenfläche schreiben.
|
||||||
|
|
||||||
|
Dazu ergänzen wir eine weitere Objektvariable `score`, die in `mouseClicked()`
|
||||||
|
erhöht wird, falls der Kreis getroffen wurde.
|
||||||
|
|
||||||
|
Um den Punktestand anzuzeigen ergänzen wir in `draw()` einen Aufruf von
|
||||||
|
{{ jdl('layers.DrawingLayer', 'text(java.lang.String,double,double,schule.ngb.zm.Options.Direction)') }}
|
||||||
|
mit dem Inhalt von `score` und den Koordinaten, an denen der Text gezeigt
|
||||||
|
werden soll.
|
||||||
|
Die Zeichenebene zeichnet im Moment alle Formen und Text ausgehend vom Zentrum
|
||||||
|
der Form. Damit der Text 10 Pixel vom Rand entfernt links oben angezeigt wird,
|
||||||
|
können wir der Text Methode (und allen anderen, die etwas zeichnen) eine {{
|
||||||
|
jdl('Options.Direction', title='Richtung') }} übergeben, die festlegt, von
|
||||||
|
welchem Ausgangspunkt (oder _Ankerpunkt_) die Form gezeichnet werden soll.
|
||||||
|
|
||||||
|
```Java
|
||||||
|
drawing.setFillColor(BLACK);
|
||||||
|
drawing.text("Punkte: "+score,10,10,NORTHWEST);
|
||||||
|
```
|
||||||
|
|
||||||
|
<figure markdown>
|
||||||
|
{ width=400 }
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
??? example "Quelltext"
|
||||||
|
|
||||||
|
```Java
|
||||||
|
import schule.ngb.zm.Zeichenmaschine;
|
||||||
|
|
||||||
|
public class Shapes extends Zeichenmaschine {
|
||||||
|
|
||||||
|
private int moleRadius = 20;
|
||||||
|
|
||||||
|
private int moleX;
|
||||||
|
|
||||||
|
private int moleY;
|
||||||
|
|
||||||
|
private double moleTime;
|
||||||
|
|
||||||
|
private int score = 0;
|
||||||
|
|
||||||
|
public Shapes() {
|
||||||
|
super(800, 800, "Shapes");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
background.setColor(BLUE);
|
||||||
|
|
||||||
|
drawing.noStroke();
|
||||||
|
drawing.setFontSize(24);
|
||||||
|
|
||||||
|
randomizeMole();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void randomizeMole() {
|
||||||
|
moleX = random(moleRadius*2, canvasWidth - moleRadius*2);
|
||||||
|
moleY = random(moleRadius*2, canvasHeight - moleRadius*2);
|
||||||
|
moleTime = 3.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update( double delta ) {
|
||||||
|
moleTime -= delta;
|
||||||
|
|
||||||
|
if( moleTime <= 0 ) {
|
||||||
|
randomizeMole();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw() {
|
||||||
|
drawing.clear();
|
||||||
|
|
||||||
|
drawing.setFillColor(255, 223, 34);
|
||||||
|
drawing.circle(moleX, moleY, moleRadius * (moleTime / 3.0));
|
||||||
|
|
||||||
|
drawing.setFillColor(BLACK);
|
||||||
|
drawing.text("Punkte: " + score, 10, 10, NORTHWEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseClicked() {
|
||||||
|
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
|
||||||
|
score += 1;
|
||||||
|
randomizeMole();
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main( String[] args ) {
|
||||||
|
new Shapes();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Wie es weitergehen kann
|
||||||
|
|
||||||
|
In diesem Schnellstart-Tutorial hast Du die Grundlagen der _Zeichenmaschine_
|
||||||
|
gelernt. Um weiterzumachen, kannst Du versuchen, das Whack-a-mole Spiel um diese
|
||||||
|
Funktionen zu erweitern:
|
||||||
|
|
||||||
|
- Mehrere "Maulwürfe" gleichzeitig.
|
||||||
|
- Unterschiedliche Zeiten pro Maulwurf.
|
||||||
|
|
||||||
|
Wenn Du mehr über die Möglichkeiten lernen möchtest, die Dir die
|
||||||
|
_Zeichenmaschine_ bereitstellt, kannst Du Dir die weiteren Tutorials in dieser
|
||||||
|
Dokumentation ansehen. Ein guter Startpunkt ist das
|
||||||
|
[Aquarium](tutorials/aquarium/aquarium1.md).
|
||||||
|
|
||||||
|
Viele verschiedene Beispiele, ohne detailliertes Tutorial, findest Du in der
|
||||||
|
Kategorie Beispiele und auf GitHub im Repository
|
||||||
|
[jneug/zeichenmaschine-examples](https://github.com/jneug/zeichenmaschine-examples).
|
||||||
19
docs/tutorials/aquarium/aquarium1.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Tutorial: Aquarium
|
||||||
|
|
||||||
|
In diesem Tutorial wollen wir mithilfe der _Zeichenmaschine_ ein (bonbonbuntes)
|
||||||
|
interaktives Aquarium entwickeln. Dabei werden wir in verschiedenen Ausbaustufen
|
||||||
|
zunächst das System Modellieren und dann implementieren.
|
||||||
|
|
||||||
|
!!! info "Mein bonbonbuntes Aquarium"
|
||||||
|
|
||||||
|
Das Projekt [Mein bonbonbuntes Aquarium](http://blog.schockwellenreiter.de/2021/02/2021021201.html)
|
||||||
|
stammt ursprünglich aus dem Blog [Schockwellenreiter](http://blog.schockwellenreiter.de)
|
||||||
|
von [Jörg Kantel](http://cognitiones.kantel-chaos-team.de/cv.html).
|
||||||
|
|
||||||
|
Das Endprodukt soll folgendes umfassen:
|
||||||
|
|
||||||
|
- Darstellung eines hübschen Aquariums mit Fischen, die hin und her schwimmen.
|
||||||
|
- Zur Darstellung wollen wir wie im Original die Sprites aus dem [Fish Pack von Kenny.nl](https://www.kenney.nl/assets/fish-pack) nutzen.
|
||||||
|
- Das Aquarium soll durch passende Geräusche untermalt werden.
|
||||||
|
- Bei einem Klick in das Aquarium soll ein zufälliger Fisch erscheinen.
|
||||||
|
- Bei einem Druck auf die Leertaste soll ein Hai durch das Aquarium schwimmen und alle Fische auf seinem Weg auffressen.
|
||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
83
mkdocs.yml
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
site_name: Zeichenmaschine.xyz
|
||||||
|
site_description: Eine kleine Java-Bibliothek für grafische Programmierung im Informatikunterricht.
|
||||||
|
site_author: J. Neugebauer
|
||||||
|
repo_url: https://github.com/jneug/zeichenmaschine
|
||||||
|
repo_name: jneug/zeichenmaschine
|
||||||
|
|
||||||
|
site_dir: build/docs/site
|
||||||
|
|
||||||
|
theme:
|
||||||
|
name: material
|
||||||
|
custom_dir: docs/home_override/
|
||||||
|
language: de
|
||||||
|
logo: assets/icon_64.png
|
||||||
|
favicon: assets/icon_32.png
|
||||||
|
features:
|
||||||
|
- content.code.annotate
|
||||||
|
- navigation.top
|
||||||
|
- navigation.tracking
|
||||||
|
- search.suggest
|
||||||
|
font: false
|
||||||
|
palette:
|
||||||
|
- media: "(prefers-color-scheme: light)"
|
||||||
|
scheme: default
|
||||||
|
primary: blue
|
||||||
|
accent: deep orange
|
||||||
|
toggle:
|
||||||
|
icon: material/weather-sunny
|
||||||
|
name: Dunkles Design aktivieren
|
||||||
|
- media: "(prefers-color-scheme: dark)"
|
||||||
|
scheme: slate
|
||||||
|
primary: blue
|
||||||
|
accent: deep orange
|
||||||
|
toggle:
|
||||||
|
icon: material/weather-night
|
||||||
|
name: Helles Design aktivieren
|
||||||
|
extra_css:
|
||||||
|
- assets/zmstyles.css
|
||||||
|
|
||||||
|
nav:
|
||||||
|
- Einführung: einfuehrung.md
|
||||||
|
- Schnellstart: schnellstart.md
|
||||||
|
- Installation: installation.md
|
||||||
|
- Tutorials:
|
||||||
|
- Aquarium: tutorials/aquarium/aquarium1.md
|
||||||
|
- Beispiele:
|
||||||
|
- Mondrian: beispiele/mondrian.md
|
||||||
|
|
||||||
|
markdown_extensions:
|
||||||
|
- admonition
|
||||||
|
- attr_list
|
||||||
|
- def_list
|
||||||
|
- footnotes
|
||||||
|
- md_in_html
|
||||||
|
- toc:
|
||||||
|
permalink: true
|
||||||
|
- pymdownx.magiclink
|
||||||
|
- pymdownx.betterem:
|
||||||
|
smart_enable: all
|
||||||
|
- pymdownx.caret
|
||||||
|
- pymdownx.smartsymbols
|
||||||
|
- pymdownx.emoji:
|
||||||
|
emoji_index: !!python/name:materialx.emoji.twemoji
|
||||||
|
emoji_generator: !!python/name:materialx.emoji.to_svg
|
||||||
|
- pymdownx.highlight:
|
||||||
|
anchor_linenums: true
|
||||||
|
- pymdownx.inlinehilite
|
||||||
|
- pymdownx.snippets
|
||||||
|
- pymdownx.details
|
||||||
|
- pymdownx.superfences:
|
||||||
|
custom_fences:
|
||||||
|
- name: mermaid
|
||||||
|
class: mermaid
|
||||||
|
format: !!python/name:pymdownx.superfences.fence_code_format
|
||||||
|
plugins:
|
||||||
|
- search:
|
||||||
|
lang: de
|
||||||
|
separator: '[\s\-\.]'
|
||||||
|
- macros:
|
||||||
|
module_name: docs/macros
|
||||||
|
|
||||||
|
extra:
|
||||||
|
javadoc_url: https://zeichenmaschine.xyz/docs/
|
||||||
|
javadoc_default_package: schule.ngb.zm
|
||||||
25
requirements.txt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
certifi==2022.9.24
|
||||||
|
charset-normalizer==2.1.1
|
||||||
|
click==8.1.3
|
||||||
|
ghp-import==2.1.0
|
||||||
|
idna==3.4
|
||||||
|
Jinja2==3.1.2
|
||||||
|
Markdown==3.3.7
|
||||||
|
MarkupSafe==2.1.1
|
||||||
|
mergedeep==1.3.4
|
||||||
|
mkdocs==1.4.1
|
||||||
|
mkdocs-macros-plugin==0.7.0
|
||||||
|
mkdocs-material==8.5.6
|
||||||
|
mkdocs-material-extensions==1.0.3
|
||||||
|
packaging==21.3
|
||||||
|
Pygments==2.13.0
|
||||||
|
pymdown-extensions==9.6
|
||||||
|
pyparsing==3.0.9
|
||||||
|
python-dateutil==2.8.2
|
||||||
|
PyYAML==6.0
|
||||||
|
pyyaml_env_tag==0.1
|
||||||
|
requests==2.28.1
|
||||||
|
six==1.16.0
|
||||||
|
termcolor==2.0.1
|
||||||
|
urllib3==1.26.12
|
||||||
|
watchdog==2.1.9
|
||||||
9
src/main/java/overview.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<HTML>
|
||||||
|
<BODY>
|
||||||
|
Die Zeichenmaschine
|
||||||
|
<p>
|
||||||
|
<h1>Die Zeichenmaschine</h1>
|
||||||
|
|
||||||
|
@author J. Neugebauer
|
||||||
|
</BODY>
|
||||||
|
</HTML>
|
||||||
194
src/main/java/schule/ngb/zm/BasicDrawable.java
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
package schule.ngb.zm;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.MultipleGradientPaint;
|
||||||
|
import java.awt.Paint;
|
||||||
|
import java.awt.Stroke;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basisimplementierung der {@link Strokeable} und {@link Fillable} APIs.
|
||||||
|
*
|
||||||
|
* Die Klasse bietet eine Grundlage zur Implementierung eigener Zeichenobjekte,
|
||||||
|
* die eine Füllung und eine Konturlinie haben können.
|
||||||
|
*/
|
||||||
|
public abstract class BasicDrawable extends Constants implements Strokeable, Fillable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob das Objekt gezeichnet werden soll.
|
||||||
|
*/
|
||||||
|
protected boolean visible = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aktuelle Farbe der Konturlinie oder {@code null}, wenn das Objekt ohne
|
||||||
|
* Kontur dargestellt werden soll.
|
||||||
|
*/
|
||||||
|
protected schule.ngb.zm.Color strokeColor = DEFAULT_STROKECOLOR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Dicke der Konturlinie. Wird nicht kleiner als 0.
|
||||||
|
*/
|
||||||
|
protected double strokeWeight = DEFAULT_STROKEWEIGHT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Art der Konturlinie.
|
||||||
|
*/
|
||||||
|
protected Options.StrokeType strokeType = SOLID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache für den aktuellen {@code Stroke} der Kontur. Wird nach Änderung
|
||||||
|
* einer der Kontureigenschaften auf {@code null} gesetzt und beim nächsten
|
||||||
|
* Zeichnen neu erstellt.
|
||||||
|
*/
|
||||||
|
protected Stroke stroke = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die aktuelle Füllfarbe der Form oder {@code null}, wenn das Objekt nicht
|
||||||
|
* gefüllt werden soll.
|
||||||
|
*/
|
||||||
|
protected Color fillColor = DEFAULT_FILLCOLOR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der aktuelle Farbverlauf des Objektes oder {@code null}, wenn es keinen
|
||||||
|
* Farbverlauf besitzt.
|
||||||
|
*/
|
||||||
|
protected MultipleGradientPaint fill = null;
|
||||||
|
|
||||||
|
|
||||||
|
// Implementierung Drawable Interface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob das Objekt angezeigt bzw. gezeichnet werden soll.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn das Objekt angezeigt werden soll,
|
||||||
|
* {@code false} sonst.
|
||||||
|
*/
|
||||||
|
public boolean isVisible() {
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Versteckt das Objekt.
|
||||||
|
*/
|
||||||
|
public void hide() {
|
||||||
|
visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt das Objekt an.
|
||||||
|
*/
|
||||||
|
public void show() {
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Versteckt da Objekt, wenn es derzeit angezeigt wird und zeigt es
|
||||||
|
* andernfalls an.
|
||||||
|
*/
|
||||||
|
public void toggle() {
|
||||||
|
visible = !visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void draw( Graphics2D graphics );
|
||||||
|
|
||||||
|
|
||||||
|
// Implementierung Fillable Interface
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFill( Paint fill ) {
|
||||||
|
if( fill == null ) {
|
||||||
|
this.fill = null;
|
||||||
|
} else if( fill instanceof Color ) {
|
||||||
|
this.fillColor = ((Color) fill);
|
||||||
|
this.fill = null;
|
||||||
|
} else if( fill instanceof MultipleGradientPaint ) {
|
||||||
|
this.fillColor = null;
|
||||||
|
this.fill = (MultipleGradientPaint) fill;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Paint getFill() {
|
||||||
|
if( fill != null ) {
|
||||||
|
return fill;
|
||||||
|
} else if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
||||||
|
return fillColor;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFillColor() {
|
||||||
|
return fillColor != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasGradient() {
|
||||||
|
return fill != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Color getFillColor() {
|
||||||
|
return fillColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFillColor( Color color ) {
|
||||||
|
fillColor = color;
|
||||||
|
fill = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MultipleGradientPaint getGradient() {
|
||||||
|
return fill;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Implementierung Strokeable Interface
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStroke( Stroke stroke ) {
|
||||||
|
this.stroke = stroke;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stroke getStroke() {
|
||||||
|
if( stroke == null ) {
|
||||||
|
stroke = Strokeable.createStroke(strokeType, strokeWeight);
|
||||||
|
}
|
||||||
|
return stroke;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Color getStrokeColor() {
|
||||||
|
return strokeColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStrokeColor( Color color ) {
|
||||||
|
strokeColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getStrokeWeight() {
|
||||||
|
return strokeWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStrokeWeight( double weight ) {
|
||||||
|
strokeWeight = weight;
|
||||||
|
this.stroke = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Options.StrokeType getStrokeType() {
|
||||||
|
return strokeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStrokeType( Options.StrokeType type ) {
|
||||||
|
strokeType = type;
|
||||||
|
this.stroke = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,6 +4,10 @@ import java.awt.*;
|
|||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.awt.image.ColorModel;
|
import java.awt.image.ColorModel;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repräsentiert eine Farbe in der Zeichenmaschine.
|
* Repräsentiert eine Farbe in der Zeichenmaschine.
|
||||||
@@ -14,6 +18,7 @@ import java.awt.image.ColorModel;
|
|||||||
* Eine Farbe hat außerdem einen Transparenzwert zwischen 0 (unsichtbar) und 255
|
* Eine Farbe hat außerdem einen Transparenzwert zwischen 0 (unsichtbar) und 255
|
||||||
* (deckend).
|
* (deckend).
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public class Color implements Paint {
|
public class Color implements Paint {
|
||||||
|
|
||||||
|
|
||||||
@@ -47,38 +52,67 @@ public class Color implements Paint {
|
|||||||
* Die Farbe Zeichenmaschinen-Rot.
|
* Die Farbe Zeichenmaschinen-Rot.
|
||||||
*/
|
*/
|
||||||
public static final Color RED = new Color(240, 80, 37);
|
public static final Color RED = new Color(240, 80, 37);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Farbe Rot.
|
||||||
|
*/
|
||||||
|
public static final Color PURE_RED = new Color(255, 0, 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Farbe Zeichenmaschinen-Grün.
|
* Die Farbe Zeichenmaschinen-Grün.
|
||||||
*/
|
*/
|
||||||
public static final Color GREEN = new Color(98, 199, 119);
|
public static final Color GREEN = new Color(98, 199, 119);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Farbe Grün.
|
||||||
|
*/
|
||||||
|
public static final Color PURE_GREEN = new Color(0, 255, 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Farbe Zeichenmaschinen-Blau.
|
* Die Farbe Zeichenmaschinen-Blau.
|
||||||
*/
|
*/
|
||||||
public static final Color BLUE = new Color(49, 197, 244);
|
public static final Color BLUE = new Color(43, 128, 243); // 49, 197, 244
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Farbe Blau.
|
||||||
|
*/
|
||||||
|
public static final Color PURE_BLUE = new Color(0, 0, 255);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Farbe Zeichenmaschinen-Gelb.
|
* Die Farbe Zeichenmaschinen-Gelb.
|
||||||
*/
|
*/
|
||||||
public static final Color YELLOW = new Color(248, 239, 34);
|
public static final Color YELLOW = new Color(248, 239, 34);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Farbe Gelb.
|
||||||
|
*/
|
||||||
|
public static final Color PURE_YELLOW = new Color(255, 255, 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Farbe Zeichenmaschinen-Orange.
|
* Die Farbe Zeichenmaschinen-Orange.
|
||||||
*/
|
*/
|
||||||
public static final Color ORANGE = new Color(248, 158, 80);
|
public static final Color ORANGE = new Color(248, 158, 80);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Farbe Zeichenmaschinen-Türkis.
|
* Die Farbe Zeichenmaschinen-Türkis.
|
||||||
*/
|
*/
|
||||||
public static final Color CYAN = new Color(java.awt.Color.CYAN);
|
public static final Color CYAN = new Color(java.awt.Color.CYAN);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Farbe Zeichenmaschinen-Magenta.
|
* Die Farbe Zeichenmaschinen-Magenta.
|
||||||
*/
|
*/
|
||||||
public static final Color MAGENTA = new Color(java.awt.Color.MAGENTA);
|
public static final Color MAGENTA = new Color(java.awt.Color.MAGENTA);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Farbe Zeichenmaschinen-Rosa.
|
* Die Farbe Zeichenmaschinen-Rosa.
|
||||||
*/
|
*/
|
||||||
public static final Color PINK = new Color(240, 99, 164);
|
public static final Color PINK = new Color(240, 99, 164);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Farbe Zeichenmaschinen-Lila.
|
* Die Farbe Zeichenmaschinen-Lila.
|
||||||
*/
|
*/
|
||||||
public static final Color PURPLE = new Color(101, 0, 191);
|
public static final Color PURPLE = new Color(101, 0, 191);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Farbe Zeichenmaschinen-Braun.
|
* Die Farbe Zeichenmaschinen-Braun.
|
||||||
*/
|
*/
|
||||||
@@ -88,6 +122,7 @@ public class Color implements Paint {
|
|||||||
* Die Farbe Helmholtz-Grün.
|
* Die Farbe Helmholtz-Grün.
|
||||||
*/
|
*/
|
||||||
public static final Color HGGREEN = new Color(0, 165, 81);
|
public static final Color HGGREEN = new Color(0, 165, 81);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Farbe Helmholtz-Rot.
|
* Die Farbe Helmholtz-Rot.
|
||||||
*/
|
*/
|
||||||
@@ -107,7 +142,7 @@ public class Color implements Paint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt eine graue Farbe entsprechend des Grauwertes <var>gray</var>.
|
* Erstellt eine graue Farbe entsprechend dem Grauwert {@code gray}.
|
||||||
*
|
*
|
||||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||||
*/
|
*/
|
||||||
@@ -116,8 +151,8 @@ public class Color implements Paint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt eine graue Farbe entsprechend des Grauwertes <var>gray</var> und
|
* Erstellt eine graue Farbe entsprechend dem Grauwert {@code gray} und dem
|
||||||
* des Transparentwertes <var>alpha</var>.
|
* Transparenzwert {@code alpha}.
|
||||||
*
|
*
|
||||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||||
*/
|
*/
|
||||||
@@ -126,9 +161,9 @@ public class Color implements Paint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt eine Farbe. Die Parameter <var>red</var>, <var>green</var> und
|
* Erstellt eine Farbe. Die Parameter {@code red}, {@code green} und
|
||||||
* <var>blue</var> geben die Rot-, Grün- und Blauanteile der Farbe. Die
|
* {@code blue} geben die Rot-, Grün- und Blauanteile der Farbe. Die Werte
|
||||||
* Werte liegen zwischen 0 und 255.
|
* liegen zwischen 0 und 255.
|
||||||
*
|
*
|
||||||
* @param red Rotwert zwischen 0 und 255.
|
* @param red Rotwert zwischen 0 und 255.
|
||||||
* @param green Grünwert zwischen 0 und 255.
|
* @param green Grünwert zwischen 0 und 255.
|
||||||
@@ -139,11 +174,11 @@ public class Color implements Paint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt eine Farbe. Die Parameter <var>red</var>, <var>green</var> und
|
* Erstellt eine Farbe. Die Parameter {@code red}, {@code green} und
|
||||||
* <var>blue</var> geben die Rot-, Grün- und Blauanteile der Farbe. Die
|
* {@code blue} geben die Rot-, Grün- und Blauanteile der Farbe. Die Werte
|
||||||
* Werte liegen zwischen 0 und 255.
|
* liegen zwischen 0 und 255. {@code alpha} gibt den den Transparentwert an
|
||||||
* <var>alpha</var> gibt den den Transparentwert an (auch zwischen
|
* (auch zwischen 0 und 255), wobei 0 komplett durchsichtig ist und 255
|
||||||
* 0 und 255), wobei 0 komplett durchsichtig ist und 255 komplett deckend.
|
* komplett deckend.
|
||||||
*
|
*
|
||||||
* @param red Rotwert zwischen 0 und 255.
|
* @param red Rotwert zwischen 0 und 255.
|
||||||
* @param green Grünwert zwischen 0 und 255.
|
* @param green Grünwert zwischen 0 und 255.
|
||||||
@@ -151,24 +186,24 @@ public class Color implements Paint {
|
|||||||
* @param alpha Transparentwert zwischen 0 und 255.
|
* @param alpha Transparentwert zwischen 0 und 255.
|
||||||
*/
|
*/
|
||||||
public Color( int red, int green, int blue, int alpha ) {
|
public Color( int red, int green, int blue, int alpha ) {
|
||||||
rgba = ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | ((blue & 0xFF) << 0);
|
rgba = ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | (blue & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt eine Farbe als Kopie von <var>color</var>.
|
* Erstellt eine Farbe als Kopie von {@code color}.
|
||||||
*
|
*
|
||||||
* @param color
|
* @param color Eine Farbe.
|
||||||
*/
|
*/
|
||||||
public Color( Color color ) {
|
public Color( Color color ) {
|
||||||
this(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
|
this(color.getRGBA(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt eine Farbe als Kopie von <var>color</var> und ersetzt den
|
* Erstellt eine Farbe als Kopie von {@code color} und ersetzt den
|
||||||
* Transparentwert durch <var>alpha</var>.
|
* Transparentwert durch {@code alpha}.
|
||||||
*
|
*
|
||||||
* @param color
|
* @param color Eine Farbe.
|
||||||
* @param alpha
|
* @param alpha Der neue Transparenzwert.
|
||||||
*/
|
*/
|
||||||
public Color( Color color, int alpha ) {
|
public Color( Color color, int alpha ) {
|
||||||
this(color.getRed(), color.getGreen(), color.getBlue(), alpha);
|
this(color.getRed(), color.getGreen(), color.getBlue(), alpha);
|
||||||
@@ -203,46 +238,97 @@ public class Color implements Paint {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt eine Farbe aus einem kodierten RGBA Integer-Wert.
|
* Erzeugt eine Farbe aus einem kodierten RGBA Integer-Wert.
|
||||||
|
* <p>
|
||||||
|
* Der 32-bit Integer enthält (von rechts) in Bit 1 bis 8 den Rotwert, in
|
||||||
|
* Bit 9 bis 16 Grünwert, in Bit 17 bis 24 den Blauwert und in Bit 25 bis 32
|
||||||
|
* den Transparenzwert der Farbe.
|
||||||
*
|
*
|
||||||
* @param rgba
|
* @param rgba Eine RGBA-Farbe.
|
||||||
* @return
|
* @return Ein Farbobjekt.
|
||||||
*/
|
*/
|
||||||
public static Color getRGBColor( int rgba ) {
|
public static Color getRGBColor( int rgba ) {
|
||||||
Color c = new Color(rgba, true);
|
return new Color(rgba, true);
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt eine Farbe aus Werten im
|
||||||
|
* <a href="https://de.wikipedia.org/wiki/HSV-Farbraum">HSB-Farbraum</a>.
|
||||||
|
* <p>
|
||||||
|
* {code h} beschreibt den Farbwert (engl. <em>hue</em>), {@code s} die
|
||||||
|
* Sättigung (engl. <em>saturation</em>) und {@code b} die absolute
|
||||||
|
* Helligkeit (engl. <em>brightness</em>) der Farbe. Alle Werte werden
|
||||||
|
* zwischen 0.0 und 1.0 angegeben.
|
||||||
|
*
|
||||||
|
* @param h Der Farbwert.
|
||||||
|
* @param s Die Sättigung.
|
||||||
|
* @param b Die absolute Helligkeit.
|
||||||
|
* @return Ein Farbobjekt.
|
||||||
|
* @see java.awt.Color#getHSBColor(float, float, float)
|
||||||
|
*/
|
||||||
public static Color getHSBColor( double h, double s, double b ) {
|
public static Color getHSBColor( double h, double s, double b ) {
|
||||||
return new Color(java.awt.Color.getHSBColor((float) h, (float) s, (float) b));
|
return new Color(java.awt.Color.getHSBColor((float) h, (float) s, (float) b));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt eine Farbe aus Werten im
|
||||||
|
* <a href="https://de.wikipedia.org/wiki/HSV-Farbraum">HSL-Farbraum</a>.
|
||||||
|
* <p>
|
||||||
|
* {code h} beschreibt den Farbwert (engl. <em>hue</em>), {@code s} die
|
||||||
|
* Sättigung (engl. <em>saturation</em>) und {@code l} die relative
|
||||||
|
* Helligkeit (engl. <em>lightness</em>) der Farbe. Alle Werte werden
|
||||||
|
* zwischen 0.0 und 1.0 angegeben.
|
||||||
|
*
|
||||||
|
* @param h Der Farbwert.
|
||||||
|
* @param s Die Sättigung.
|
||||||
|
* @param l Die relative Helligkeit.
|
||||||
|
* @return Ein Farbobjekt.
|
||||||
|
*/
|
||||||
public static Color getHSLColor( double h, double s, double l ) {
|
public static Color getHSLColor( double h, double s, double l ) {
|
||||||
int rgb = Color.HSLtoRGB(new float[]{(float) h, (float) s, (float) l});
|
int rgb = Color.HSLtoRGB(new float[]{(float) h, (float) s, (float) l});
|
||||||
return Color.getRGBColor(rgb);
|
return Color.getRGBColor(rgb);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Color parseString( String pColor ) {
|
/**
|
||||||
pColor = pColor.toLowerCase().strip();
|
* Erstellt aus einem Farbnamen ein Farbobjekt.
|
||||||
if( pColor.contains("red") || pColor.contains("rot") ) {
|
* <p>
|
||||||
return Color.RED.copy();
|
* Die gültigen Farbnamen können unter <a
|
||||||
} else if( pColor.contains("blue") || pColor.contains("blau") ) {
|
* href="https://htmlcolors.com/color-names">https://htmlcolors.com/color-names</a>
|
||||||
return Color.BLUE.copy();
|
* nachgeschlagen werden.
|
||||||
} else if( pColor.contains("green") || pColor.contains("grün") || pColor.contains("gruen") ) {
|
*
|
||||||
return Color.GREEN.copy();
|
* @param color Der Name einer Farbe.
|
||||||
} else if( pColor.contains("yellow") || pColor.contains("gelb") ) {
|
* @return Ein Farbobjekt.
|
||||||
return Color.YELLOW.copy();
|
*/
|
||||||
} else {
|
public static Color parseString( String color ) {
|
||||||
return new Color();
|
color = color.toLowerCase().strip();
|
||||||
|
|
||||||
|
// Parse colornames file and return first match
|
||||||
|
try( InputStream in = Color.class.getResourceAsStream("colornames.csv"); BufferedReader reader = new BufferedReader(new InputStreamReader(in)) ) {
|
||||||
|
String line;
|
||||||
|
while( (line = reader.readLine()) != null ) {
|
||||||
|
String[] parts = line.split(",");
|
||||||
|
if( parts.length == 2 ) {
|
||||||
|
if( parts[0].equals(color) ) {
|
||||||
|
return Color.parseHexcode(parts[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} catch( IOException ex ) {
|
||||||
|
// LOG?
|
||||||
|
}
|
||||||
|
return new Color();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt eine Farbe aus einem hexadezimalen Code. Der Hexcode kann sechs-
|
* Erzeugt eine Farbe aus einem hexadezimalen Code. Der Hexcode kann drei-,
|
||||||
* oder achtstellig sein (wenn ein Transparentwert vorhanden ist). Dem Code
|
* sechs- oder achtstellig sein (wenn ein Transparentwert vorhanden ist).
|
||||||
* kann ein {@code #} Zeichen vorangestellt sein.
|
* Dem Code kann ein {@code #} Zeichen vorangestellt sein, muss es aber
|
||||||
|
* nicht.
|
||||||
|
* <p>
|
||||||
|
* Bei einem dreistelligen Code wird jedes zeichen doppelt interpretiert.
|
||||||
|
* Das beduetet {@code #ABC} ist gleichbedeutend mit {@code #AABBCC}.
|
||||||
*
|
*
|
||||||
* @param hexcode
|
* @param hexcode Eine Farbe als Hexcode.
|
||||||
* @return
|
* @return Ein Farbobjekt.
|
||||||
*/
|
*/
|
||||||
public static Color parseHexcode( String hexcode ) {
|
public static Color parseHexcode( String hexcode ) {
|
||||||
if( hexcode.startsWith("#") ) {
|
if( hexcode.startsWith("#") ) {
|
||||||
@@ -259,8 +345,6 @@ public class Color implements Paint {
|
|||||||
} else if( hexcode.length() == 8 ) {
|
} else if( hexcode.length() == 8 ) {
|
||||||
alpha = Integer.valueOf(hexcode.substring(6, 8), 16);
|
alpha = Integer.valueOf(hexcode.substring(6, 8), 16);
|
||||||
hexcode = hexcode.substring(0, 6);
|
hexcode = hexcode.substring(0, 6);
|
||||||
} else {
|
|
||||||
hexcode = hexcode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Color.getRGBColor((alpha << 24) | Integer.valueOf(hexcode, 16));
|
return Color.getRGBColor((alpha << 24) | Integer.valueOf(hexcode, 16));
|
||||||
@@ -281,7 +365,7 @@ public class Color implements Paint {
|
|||||||
if( color1 == null && color2 == null ) {
|
if( color1 == null && color2 == null ) {
|
||||||
throw new IllegalArgumentException("Color.interpolate() needs at least one color to be not null.");
|
throw new IllegalArgumentException("Color.interpolate() needs at least one color to be not null.");
|
||||||
}
|
}
|
||||||
if( t < 0.0 || color2 == null ) {
|
if( (color1 != null && t < 0.0) || color2 == null ) {
|
||||||
return color1.copy();
|
return color1.copy();
|
||||||
}
|
}
|
||||||
if( t > 1.0 || color1 == null ) {
|
if( t > 1.0 || color1 == null ) {
|
||||||
@@ -307,12 +391,12 @@ public class Color implements Paint {
|
|||||||
if( c == 0 ) {
|
if( c == 0 ) {
|
||||||
h_ = 0;
|
h_ = 0;
|
||||||
} else if( max == r ) {
|
} else if( max == r ) {
|
||||||
h_ = (float) (g - b) / c;
|
h_ = (g - b) / c;
|
||||||
if( h_ < 0 ) h_ += 6.f;
|
if( h_ < 0 ) h_ += 6.f;
|
||||||
} else if( max == g ) {
|
} else if( max == g ) {
|
||||||
h_ = (float) (b - r) / c + 2.f;
|
h_ = (b - r) / c + 2.f;
|
||||||
} else if( max == b ) {
|
} else if( max == b ) {
|
||||||
h_ = (float) (r - g) / c + 4.f;
|
h_ = (r - g) / c + 4.f;
|
||||||
}
|
}
|
||||||
float h = 60.f * h_;
|
float h = 60.f * h_;
|
||||||
|
|
||||||
@@ -331,6 +415,14 @@ public class Color implements Paint {
|
|||||||
return hsl;
|
return hsl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert die Komponenten einer Farbe aus dem HSL-Farbraum in den
|
||||||
|
* RGB-Farbraum.
|
||||||
|
*
|
||||||
|
* @param hsl Die HSL-Komponenten als float-Array.
|
||||||
|
* @return Der RGBA-Farbwert.
|
||||||
|
* @see #HSLtoRGB(float[], int)
|
||||||
|
*/
|
||||||
public static int HSLtoRGB( float[] hsl ) {
|
public static int HSLtoRGB( float[] hsl ) {
|
||||||
return HSLtoRGB(hsl, 255);
|
return HSLtoRGB(hsl, 255);
|
||||||
}
|
}
|
||||||
@@ -339,9 +431,9 @@ public class Color implements Paint {
|
|||||||
* Konvertiert eine Farbe mit Komponenten im HSL-Farbraum in den
|
* Konvertiert eine Farbe mit Komponenten im HSL-Farbraum in den
|
||||||
* RGB-Farbraum.
|
* RGB-Farbraum.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Farbkomponenten werden als Float-Array übergeben. Im Index 0 steht
|
* Die Farbkomponenten werden als float-Array übergeben. Im Index 0 steht
|
||||||
* der h-Wert im Bereich 0 bis 360, Index 1 und 2 enthalten den s- und
|
* der H-Wert im Bereich 0 bis 360, Index 1 und 2 enthalten den S- und
|
||||||
* l-Wert im Bereich von 0 bis 1.
|
* L-Wert im Bereich von 0 bis 1.
|
||||||
*
|
*
|
||||||
* @param hsl Die Farbkomponenten im HSL-Farbraum.
|
* @param hsl Die Farbkomponenten im HSL-Farbraum.
|
||||||
* @param alpha Ein Transparenzwert im Bereich 0 bis 255.
|
* @param alpha Ein Transparenzwert im Bereich 0 bis 255.
|
||||||
@@ -484,16 +576,17 @@ public class Color implements Paint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
/**
|
/**
|
||||||
* Prüft, ob ein anderes Objekt zu diesem gleich ist.
|
* Prüft, ob ein anderes Objekt zu diesem gleich ist.
|
||||||
|
* <p>
|
||||||
|
* Die Methode gibt genau dann {@code true} zurück, wenn das andere Objekt
|
||||||
|
* nicht {@code null} ist, vom Typ {@code Color} ist und es dieselben Rot-,
|
||||||
|
* Grün-, Blau- und Transparenzwerte hat.
|
||||||
*
|
*
|
||||||
* Die Methode gibt genau dann {@code true} zurück, wenn das andere
|
|
||||||
* Objekt nicht {@code null} ist, vom Typ {@code Color} ist und es
|
|
||||||
* dieselben Rot-, Grün-, Blau- und Transparenzwerte hat.
|
|
||||||
* @param obj Das zu vergleichende Objekt.
|
* @param obj Das zu vergleichende Objekt.
|
||||||
* @return {@code true}, wenn die Objekte gleich sind, sonst {@code false}.
|
* @return {@code true}, wenn die Objekte gleich sind, sonst {@code false}.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public boolean equals( Object obj ) {
|
public boolean equals( Object obj ) {
|
||||||
if( obj == null ) {
|
if( obj == null ) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,26 +1,29 @@
|
|||||||
package schule.ngb.zm;
|
package schule.ngb.zm;
|
||||||
|
|
||||||
import schule.ngb.zm.anim.Easing;
|
import schule.ngb.zm.anim.Easing;
|
||||||
import schule.ngb.zm.util.io.ImageLoader;
|
|
||||||
import schule.ngb.zm.util.Noise;
|
import schule.ngb.zm.util.Noise;
|
||||||
|
import schule.ngb.zm.util.io.ImageLoader;
|
||||||
|
|
||||||
import java.awt.Cursor;
|
import java.awt.Cursor;
|
||||||
|
import java.awt.Font;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.function.DoubleUnaryOperator;
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basisklasse für die meisten Objekte der Zeichemaschine, die von Nutzern
|
* Basisklasse für die meisten Objekte der Zeichenmaschine, die von Nutzern
|
||||||
* erweitert werden können.
|
* erweitert werden können.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Konstanten stellen viele Funktionen zur einfachen Programmierung bereit
|
* Die Konstanten stellen viele Funktionen zur einfachen Programmierung bereit
|
||||||
* und enthält auch einige dynamische Werte, die von der Zeichenmaschine laufend
|
* und enthält auch einige dynamische Werte, die von der Zeichenmaschine laufend
|
||||||
* aktuell gehalten werden (beispielsweise {@link #runtime}.
|
* aktuell gehalten werden (beispielsweise {@link #runtime}).
|
||||||
* <p>
|
* <p>
|
||||||
* Für die Implementierung eigener Klassen ist es oft hilfreich von
|
* Für die Implementierung eigener Klassen ist es oft hilfreich von
|
||||||
* {@code Constants} zu erben, um die Methoden und Konstanten einfach im
|
* {@code Constants} zu erben, um die Methoden und Konstanten einfacher im
|
||||||
* Programm nutzen zu können.
|
* Programm nutzen zu können.
|
||||||
* <pre><code>
|
* <pre><code>
|
||||||
* class MyClass extends Constants {
|
* class MyClass extends Constants {
|
||||||
@@ -64,13 +67,49 @@ public class Constants {
|
|||||||
/**
|
/**
|
||||||
* Patchversion der Zeichenmaschine.
|
* Patchversion der Zeichenmaschine.
|
||||||
*/
|
*/
|
||||||
public static final int APP_VERSION_REV = 22;
|
public static final int APP_VERSION_REV = 32;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Version der Zeichenmaschine als Text-String.
|
* Version der Zeichenmaschine als Text-String.
|
||||||
*/
|
*/
|
||||||
public static final String APP_VERSION = APP_VERSION_MAJ + "." + APP_VERSION_MIN + "." + APP_VERSION_REV;
|
public static final String APP_VERSION = APP_VERSION_MAJ + "." + APP_VERSION_MIN + "." + APP_VERSION_REV;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt an, ob die Zeichenmaschine unter macOS gestartet wurde.
|
||||||
|
*/
|
||||||
|
public static final boolean MACOS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt an, ob die Zeichenmaschine unter Windows gestartet wurde.
|
||||||
|
*/
|
||||||
|
public static final boolean WINDOWS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt an, ob die Zeichenmaschine unter Linux gestartet wurde.
|
||||||
|
*/
|
||||||
|
public static final boolean LINUX;
|
||||||
|
|
||||||
|
static {
|
||||||
|
final String name = System.getProperty("os.name");
|
||||||
|
|
||||||
|
if( name.contains("Mac") ) {
|
||||||
|
MACOS = true;
|
||||||
|
WINDOWS = false;
|
||||||
|
LINUX = false;
|
||||||
|
} else if( name.contains("Windows") ) {
|
||||||
|
MACOS = false;
|
||||||
|
WINDOWS = true;
|
||||||
|
LINUX = false;
|
||||||
|
} else if( name.equals("Linux") ) { // true for the ibm vm
|
||||||
|
MACOS = false;
|
||||||
|
WINDOWS = false;
|
||||||
|
LINUX = true;
|
||||||
|
} else {
|
||||||
|
MACOS = false;
|
||||||
|
WINDOWS = false;
|
||||||
|
LINUX = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standardbreite eines Zeichenfensters.
|
* Standardbreite eines Zeichenfensters.
|
||||||
@@ -405,6 +444,21 @@ public class Constants {
|
|||||||
*/
|
*/
|
||||||
public static final double TWO_PI = Math.PI * 2.0;
|
public static final double TWO_PI = Math.PI * 2.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konstante für fette Schrift.
|
||||||
|
*/
|
||||||
|
public static final int BOLD = Font.BOLD;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konstante für kursive Schrift.
|
||||||
|
*/
|
||||||
|
public static final int ITALIC = Font.ITALIC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konstante für normale Schrift.
|
||||||
|
*/
|
||||||
|
public static final int PLAIN = Font.PLAIN;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Globale Variablen, die von allen Klassen genutzt werden dürfen. Änderungen
|
* Globale Variablen, die von allen Klassen genutzt werden dürfen. Änderungen
|
||||||
* wirken sich auf die aktuelle Zeichenmaschine aus und sollten nur von der
|
* wirken sich auf die aktuelle Zeichenmaschine aus und sollten nur von der
|
||||||
@@ -731,6 +785,30 @@ public class Constants {
|
|||||||
|
|
||||||
// Mathematische Funktionen
|
// Mathematische Funktionen
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Berechnet das Minimum aller angegebenen Werte.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* int minimum = min(1, 5, 3); // 1
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param numbers Die Werte, aus denen das Minimum ermittelt werden soll.
|
||||||
|
* @return Das Minimum der Werte.
|
||||||
|
* @throws IllegalArgumentException Wenn die Eingabe {@code null} oder leer
|
||||||
|
* ist.
|
||||||
|
*/
|
||||||
|
public static final int min( int... numbers ) {
|
||||||
|
if( numbers == null || numbers.length == 0 ) {
|
||||||
|
throw new IllegalArgumentException("Array may not be <null> or empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int min = numbers[0];
|
||||||
|
for( int i = 1; i < numbers.length; i++ ) {
|
||||||
|
min = Math.min(min, numbers[i]);
|
||||||
|
}
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Berechnet das Minimum aller angegebenen Werte.
|
* Berechnet das Minimum aller angegebenen Werte.
|
||||||
*
|
*
|
||||||
@@ -755,6 +833,30 @@ public class Constants {
|
|||||||
return min;
|
return min;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Berechnet das Maximum aller angegebenen Werte.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* double maximum = max(1, 5, 3); // 5
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param numbers Die Werte, aus denen das Maximum ermittelt werden soll.
|
||||||
|
* @return Das Maximum der Werte.
|
||||||
|
* @throws IllegalArgumentException Wenn die Eingabe {@code null} oder leer
|
||||||
|
* ist.
|
||||||
|
*/
|
||||||
|
public static final int max( int... numbers ) {
|
||||||
|
if( numbers == null || numbers.length == 0 ) {
|
||||||
|
throw new IllegalArgumentException("Array may not be <null> or empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int max = numbers[0];
|
||||||
|
for( int i = 1; i < numbers.length; i++ ) {
|
||||||
|
max = Math.max(max, numbers[i]);
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Berechnet das Maximum aller angegebenen Werte.
|
* Berechnet das Maximum aller angegebenen Werte.
|
||||||
*
|
*
|
||||||
@@ -1000,7 +1102,7 @@ public class Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ermittelt den Arkuskosinus der angegebenen Zahl.
|
* Ermittelt den Arcuskosinus der angegebenen Zahl.
|
||||||
*
|
*
|
||||||
* @param x Eine Zahl.
|
* @param x Eine Zahl.
|
||||||
* @return {@code acos(x)}.
|
* @return {@code acos(x)}.
|
||||||
@@ -1010,7 +1112,7 @@ public class Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ermittelt den Arkusktangens der angegebenen Zahl.
|
* Ermittelt den Arcusktangens der angegebenen Zahl.
|
||||||
*
|
*
|
||||||
* @param x Eine Zahl.
|
* @param x Eine Zahl.
|
||||||
* @return {@code atan(x)}.
|
* @return {@code atan(x)}.
|
||||||
@@ -1150,6 +1252,12 @@ public class Constants {
|
|||||||
return interpolate(toMin, toMax, (value - fromMin) / (fromMax - fromMin));
|
return interpolate(toMin, toMax, (value - fromMin) / (fromMax - fromMin));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final double distance( double fromX, double fromY, double toX, double toY ) {
|
||||||
|
double diffX = toX - fromX;
|
||||||
|
double diffY = toY - fromY;
|
||||||
|
return sqrt(diffX * diffX + diffY * diffY);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Geteilte {@code Random}-Instanz für einheitliche Zufallszahlen.
|
* Geteilte {@code Random}-Instanz für einheitliche Zufallszahlen.
|
||||||
*/
|
*/
|
||||||
@@ -1212,7 +1320,8 @@ public class Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt eine ganze Pseudozufallszahl zwischen {@code 0} und {@code max}.
|
* Erzeugt eine ganze Pseudozufallszahl zwischen {@code 0} und {@code max}
|
||||||
|
* (einschließlich der Grenzen).
|
||||||
*
|
*
|
||||||
* @param max Obere Grenze.
|
* @param max Obere Grenze.
|
||||||
* @return Eine Zufallszahl.
|
* @return Eine Zufallszahl.
|
||||||
@@ -1223,7 +1332,7 @@ public class Constants {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt eine ganze Pseudozufallsganzzahl zwischen {@code min} und
|
* Erzeugt eine ganze Pseudozufallsganzzahl zwischen {@code min} und
|
||||||
* {@code max}.
|
* {@code max} (einschließlich der Grenzen).
|
||||||
*
|
*
|
||||||
* @param min Untere Grenze.
|
* @param min Untere Grenze.
|
||||||
* @param max Obere Grenze.
|
* @param max Obere Grenze.
|
||||||
@@ -1280,6 +1389,26 @@ public class Constants {
|
|||||||
return getRandom().nextGaussian();
|
return getRandom().nextGaussian();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wählt ein zufälliges Element aus dem Array aus.
|
||||||
|
*
|
||||||
|
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||||
|
* @return Ein zufälliges Element aus dem Array.
|
||||||
|
*/
|
||||||
|
public static final int choice( int... values ) {
|
||||||
|
return values[random(0, values.length - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wählt ein zufälliges Element aus dem Array aus.
|
||||||
|
*
|
||||||
|
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||||
|
* @return Ein zufälliges Element aus dem Array.
|
||||||
|
*/
|
||||||
|
public static final double choice( double... values ) {
|
||||||
|
return values[random(0, values.length - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wählt ein zufälliges Element aus dem Array aus.
|
* Wählt ein zufälliges Element aus dem Array aus.
|
||||||
*
|
*
|
||||||
@@ -1287,7 +1416,8 @@ public class Constants {
|
|||||||
* @param <T> Datentyp des Elements.
|
* @param <T> Datentyp des Elements.
|
||||||
* @return Ein zufälliges Element aus dem Array.
|
* @return Ein zufälliges Element aus dem Array.
|
||||||
*/
|
*/
|
||||||
public static final <T> T choice( T[] values ) {
|
@SafeVarargs
|
||||||
|
public static final <T> T choice( T... values ) {
|
||||||
return values[random(0, values.length - 1)];
|
return values[random(0, values.length - 1)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1296,15 +1426,149 @@ public class Constants {
|
|||||||
*
|
*
|
||||||
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||||
* @param n Anzahl der auszuwählenden Elemente.
|
* @param n Anzahl der auszuwählenden Elemente.
|
||||||
|
* @param unique Bei {@code true} werden Elemente im Array nur maximal
|
||||||
|
* einmal ausgewählt (Ziehen ohne Zurücklegen).
|
||||||
|
* @return Ein zufälliges Element aus dem Array.
|
||||||
|
* @throws IllegalArgumentException Wenn {@code unique == true} und
|
||||||
|
* {@code values.length < n}, also nicht
|
||||||
|
* genug Werte zur Wahl stehen.
|
||||||
|
*/
|
||||||
|
public static final int[] choice( int[] values, int n, boolean unique ) {
|
||||||
|
if( unique && values.length < n )
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Need at least <%d> values to choose <%d> unique values (<%d> given).", n, n, values.length)
|
||||||
|
);
|
||||||
|
|
||||||
|
int[] result = new int[n];
|
||||||
|
int[] valuesCopy = Arrays.copyOf(values, values.length);
|
||||||
|
for( int i = 0; i < n; i++ ) {
|
||||||
|
int j = random(0, valuesCopy.length - 1);
|
||||||
|
int l = valuesCopy.length - 1;
|
||||||
|
|
||||||
|
result[i] = valuesCopy[j];
|
||||||
|
valuesCopy[j] = valuesCopy[l];
|
||||||
|
valuesCopy[l] = result[i];
|
||||||
|
|
||||||
|
if( unique )
|
||||||
|
valuesCopy = Arrays.copyOf(valuesCopy, l);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wählt die angegebene Anzahl Elemente aus dem Array aus.
|
||||||
|
*
|
||||||
|
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||||
|
* @param n Anzahl der auszuwählenden Elemente.
|
||||||
|
* @param unique Bei {@code true} werden Elemente im Array nur maximal
|
||||||
|
* einmal ausgewählt (Ziehen ohne Zurücklegen).
|
||||||
|
* @return Ein zufälliges Element aus dem Array.
|
||||||
|
* @throws IllegalArgumentException Wenn {@code unique == true} und
|
||||||
|
* {@code values.length < n}, also nicht
|
||||||
|
* genug Werte zur Wahl stehen.
|
||||||
|
*/
|
||||||
|
public static final double[] choice( double[] values, int n, boolean unique ) {
|
||||||
|
if( unique && values.length < n )
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Need at least <%d> values to choose <%d> unique values (<%d> given).", n, n, values.length)
|
||||||
|
);
|
||||||
|
|
||||||
|
double[] result = new double[n];
|
||||||
|
double[] valuesCopy = Arrays.copyOf(values, values.length);
|
||||||
|
for( int i = 0; i < n; i++ ) {
|
||||||
|
int j = random(0, valuesCopy.length - 1);
|
||||||
|
int l = valuesCopy.length - 1;
|
||||||
|
|
||||||
|
result[i] = valuesCopy[j];
|
||||||
|
valuesCopy[j] = valuesCopy[l];
|
||||||
|
valuesCopy[l] = result[i];
|
||||||
|
|
||||||
|
if( unique )
|
||||||
|
valuesCopy = Arrays.copyOf(valuesCopy, l);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wählt die angegebene Anzahl Elemente aus dem Array aus.
|
||||||
|
*
|
||||||
|
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||||
|
* @param n Anzahl der auszuwählenden Elemente.
|
||||||
|
* @param unique Bei {@code true} werden Elemente im Array nur maximal
|
||||||
|
* einmal ausgewählt (Ziehen ohne Zurücklegen).
|
||||||
* @param <T> Datentyp der Elemente.
|
* @param <T> Datentyp der Elemente.
|
||||||
* @return Ein zufälliges Element aus dem Array.
|
* @return Ein zufälliges Element aus dem Array.
|
||||||
|
* @throws IllegalArgumentException Wenn {@code unique == true} und
|
||||||
|
* {@code values.length < n}, also nicht
|
||||||
|
* genug Werte zur Wahl stehen.
|
||||||
*/
|
*/
|
||||||
public static final <T> T[] choice( T[] values, int n ) {
|
public static final <T> T[] choice( T[] values, int n, boolean unique ) {
|
||||||
Object[] result = new Object[n];
|
if( unique && values.length < n )
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Need at least <%d> values to choose <%d> unique values (<%d> given).", n, n, values.length)
|
||||||
|
);
|
||||||
|
|
||||||
|
T[] result = Arrays.copyOf(values, n);
|
||||||
|
T[] valuesCopy = Arrays.copyOf(values, values.length);
|
||||||
for( int i = 0; i < n; i++ ) {
|
for( int i = 0; i < n; i++ ) {
|
||||||
result[i] = choice(values);
|
int last = valuesCopy.length - 1;
|
||||||
|
int j = random(0, last);
|
||||||
|
|
||||||
|
result[i] = valuesCopy[j];
|
||||||
|
valuesCopy[j] = valuesCopy[last];
|
||||||
|
valuesCopy[last] = result[i];
|
||||||
|
|
||||||
|
if( unique )
|
||||||
|
valuesCopy = Arrays.copyOf(valuesCopy, last);
|
||||||
}
|
}
|
||||||
return (T[]) result;
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bringt die Zahlen im Array in eine zufällige Reihenfolge.
|
||||||
|
*
|
||||||
|
* @param values Ein Array mit Zahlen, die gemischt werden sollen.
|
||||||
|
* @return Das Array in zufälliger Reihenfolge.
|
||||||
|
*/
|
||||||
|
public static final int[] shuffle( int[] values ) {
|
||||||
|
for( int i = 0; i < values.length - 1; i++ ) {
|
||||||
|
int j = random(i, values.length - 1);
|
||||||
|
int tmp = values[i];
|
||||||
|
values[i] = values[j];
|
||||||
|
values[j] = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bringt die Zahlen im Array in eine zufällige Reihenfolge.
|
||||||
|
*
|
||||||
|
* @param values Ein Array mit Zahlen, die gemischt werden sollen.
|
||||||
|
* @return Das Array in zufälliger Reihenfolge.
|
||||||
|
*/
|
||||||
|
public static final double[] shuffle( double[] values ) {
|
||||||
|
for( int i = 0; i < values.length - 1; i++ ) {
|
||||||
|
int j = random(i, values.length - 1);
|
||||||
|
double tmp = values[i];
|
||||||
|
values[i] = values[j];
|
||||||
|
values[j] = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bringt die Werte im Array in eine zufällige Reihenfolge.
|
||||||
|
*
|
||||||
|
* @param values Ein Array mit Werte, die gemischt werden sollen.
|
||||||
|
* @param <T> Datentyp der Elemente.
|
||||||
|
* @return Das Array in zufälliger Reihenfolge.
|
||||||
|
*/
|
||||||
|
public static final <T> T[] shuffle( T[] values ) {
|
||||||
|
java.util.List<T> valueList = Arrays.asList(values);
|
||||||
|
Collections.shuffle(valueList, random);
|
||||||
|
return valueList.toArray(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1473,38 +1737,93 @@ public class Constants {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen char-Wert in einen double-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der char-Wert.
|
||||||
|
* @return Ein double-Wert.
|
||||||
|
*/
|
||||||
public static final double asDouble( char value ) {
|
public static final double asDouble( char value ) {
|
||||||
return (double) value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen byte-Wert in einen double-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der byte-Wert.
|
||||||
|
* @return Ein double-Wert.
|
||||||
|
*/
|
||||||
public static final double asDouble( byte value ) {
|
public static final double asDouble( byte value ) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen short-Wert in einen double-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der short-Wert.
|
||||||
|
* @return Ein double-Wert.
|
||||||
|
*/
|
||||||
public static final double asDouble( short value ) {
|
public static final double asDouble( short value ) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen long-Wert in einen double-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der long-Wert.
|
||||||
|
* @return Ein double-Wert.
|
||||||
|
*/
|
||||||
public static final double asDouble( long value ) {
|
public static final double asDouble( long value ) {
|
||||||
return (double) value;
|
return (double) value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen double-Wert in einen double-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der double-Wert.
|
||||||
|
* @return Ein double-Wert.
|
||||||
|
*/
|
||||||
public static final double asDouble( double value ) {
|
public static final double asDouble( double value ) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen float-Wert in einen double-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der float-Wert.
|
||||||
|
* @return Ein double-Wert.
|
||||||
|
*/
|
||||||
public static final double asDouble( float value ) {
|
public static final double asDouble( float value ) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen int-Wert in einen double-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der int-Wert.
|
||||||
|
* @return Ein double-Wert.
|
||||||
|
*/
|
||||||
public static final double asDouble( int value ) {
|
public static final double asDouble( int value ) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen boolean-Wert in einen double-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der boolean-Wert.
|
||||||
|
* @return Ein double-Wert.
|
||||||
|
*/
|
||||||
public static final double asDouble( boolean value ) {
|
public static final double asDouble( boolean value ) {
|
||||||
return value ? 1.0 : 0.0;
|
return value ? 1.0 : 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen String in einen double-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der String.
|
||||||
|
* @return Ein double-Wert.
|
||||||
|
* @see Double#parseDouble(String)
|
||||||
|
*/
|
||||||
public static final double asDouble( String value ) {
|
public static final double asDouble( String value ) {
|
||||||
try {
|
try {
|
||||||
return Double.parseDouble(value);
|
return Double.parseDouble(value);
|
||||||
@@ -1513,38 +1832,93 @@ public class Constants {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen char-Wert in einen boolean-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der char-Wert.
|
||||||
|
* @return Ein boolean-Wert.
|
||||||
|
*/
|
||||||
public static final boolean asBool( char value ) {
|
public static final boolean asBool( char value ) {
|
||||||
return value != 0;
|
return value != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen byte-Wert in einen boolean-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der byte-Wert.
|
||||||
|
* @return Ein boolean-Wert.
|
||||||
|
*/
|
||||||
public static final boolean asBool( byte value ) {
|
public static final boolean asBool( byte value ) {
|
||||||
return value != 0;
|
return value != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen short-Wert in einen boolean-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der short-Wert.
|
||||||
|
* @return Ein boolean-Wert.
|
||||||
|
*/
|
||||||
public static final boolean asBool( short value ) {
|
public static final boolean asBool( short value ) {
|
||||||
return value != 0;
|
return value != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen int-Wert in einen boolean-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der int-Wert.
|
||||||
|
* @return Ein boolean-Wert.
|
||||||
|
*/
|
||||||
public static final boolean asBool( int value ) {
|
public static final boolean asBool( int value ) {
|
||||||
return value != 0;
|
return value != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen long-Wert in einen boolean-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der long-Wert.
|
||||||
|
* @return Ein boolean-Wert.
|
||||||
|
*/
|
||||||
public static final boolean asBool( long value ) {
|
public static final boolean asBool( long value ) {
|
||||||
return value != 0L;
|
return value != 0L;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen double-Wert in einen boolean-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der double-Wert.
|
||||||
|
* @return Ein boolean-Wert.
|
||||||
|
*/
|
||||||
public static final boolean asBool( double value ) {
|
public static final boolean asBool( double value ) {
|
||||||
return value != 0.0;
|
return value != 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen float-Wert in einen boolean-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der float-Wert.
|
||||||
|
* @return Ein boolean-Wert.
|
||||||
|
*/
|
||||||
public static final boolean asBool( float value ) {
|
public static final boolean asBool( float value ) {
|
||||||
return value != 0.0f;
|
return value != 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen boolean-Wert in einen boolean-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der boolean-Wert.
|
||||||
|
* @return Ein boolean-Wert.
|
||||||
|
*/
|
||||||
public static final boolean asBool( boolean value ) {
|
public static final boolean asBool( boolean value ) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert einen String in einen boolean-Wert.
|
||||||
|
*
|
||||||
|
* @param value Der String.
|
||||||
|
* @return Ein boolean-Wert.
|
||||||
|
* @see Boolean#parseBoolean(String)
|
||||||
|
*/
|
||||||
public static final boolean asBool( String value ) {
|
public static final boolean asBool( String value ) {
|
||||||
return Boolean.parseBoolean(value);
|
return Boolean.parseBoolean(value);
|
||||||
}
|
}
|
||||||
@@ -1605,7 +1979,7 @@ public class Constants {
|
|||||||
return Integer.valueOf(binary, 16);
|
return Integer.valueOf(binary, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Konstants für Key events (Copied from KeyEvent)
|
// Konstanten für Key events (Copied from KeyEvent)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constant for the ENTER virtual key.
|
* Constant for the ENTER virtual key.
|
||||||
|
|||||||
279
src/main/java/schule/ngb/zm/Fillable.java
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
package schule.ngb.zm;
|
||||||
|
|
||||||
|
import schule.ngb.zm.shapes.Shape;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Drawable} Klassen, die mit einer Füllung versehen werden können.
|
||||||
|
* <p>
|
||||||
|
* Das {@code Fillable} Interface dient hauptsächlich zur Vereinheitlichung der
|
||||||
|
* API für Füllungen. Durch Implementation wird sichergestellt, dass alle
|
||||||
|
* Objekte, die eine Füllung haben können, dieselben Methoden zur Verfügung
|
||||||
|
* stellen. Wenn eine {@link Shape} eine
|
||||||
|
* {@link Fillable#setFillColor(Color, int)} Methode hat, dann sollte auch eine
|
||||||
|
* {@link schule.ngb.zm.layers.TurtleLayer.Turtle} dieselbe Methode anbieten. Im
|
||||||
|
* Einzelfall kann es sinnvoll sein, weitere Methoden für Füllungen zur
|
||||||
|
* Verfügung zu stellen. Allerdings sollte davon nach Möglichkeit zugunsten
|
||||||
|
* einer einheitlichen API abgesehen werden.
|
||||||
|
* <p>
|
||||||
|
* Das Äquivalent für Konturlinien stellt {@link Strokeable} dar.
|
||||||
|
* <p>
|
||||||
|
* Im einfachsten Fall reicht es {@link #setFill(Paint)} und {@link #getFill()}
|
||||||
|
* zu implementieren. Die anderen Methoden besitzen Standardimplementierungen,
|
||||||
|
* die sich auf die beiden Methoden beziehen. Allerdings ist es in vielen Fällen
|
||||||
|
* sinnvoll, einige der Methoden gezielt zu überschreiben, um sie an spezifische
|
||||||
|
* Situationen anzupassen.
|
||||||
|
*/
|
||||||
|
public interface Fillable extends Drawable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllung direkt auf das angegebene {@code Paint}-Objekt.
|
||||||
|
*
|
||||||
|
* @param fill Die neue Füllung.
|
||||||
|
*/
|
||||||
|
void setFill( Paint fill );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die aktuell gesetzte Füllung zurück.
|
||||||
|
* <p>
|
||||||
|
* Die Art der Füllung kann anhand der Abfragen {@link #hasFillColor()} und
|
||||||
|
* {@link #hasGradient()} ermittelt werden.
|
||||||
|
*
|
||||||
|
* @return Die aktuelle Füllung.
|
||||||
|
*/
|
||||||
|
Paint getFill();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt an, ob aktuell eine sichtbare Füllung konfiguriert ist.
|
||||||
|
* <p>
|
||||||
|
* Eine Füllung gilt als sichtbar, wenn eine nciht transparente Füllfarbe
|
||||||
|
* oder ein Farbverlauf eingestellt ist.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn die Füllung sichtbar ist, {@code false} sonst.
|
||||||
|
*/
|
||||||
|
default boolean hasFill() {
|
||||||
|
return (hasFillColor() && getFillColor().getAlpha() > 0) || hasGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt an, ob eine Füllfarbe konfiguriert ist.
|
||||||
|
* <p>
|
||||||
|
* Im Gegensatz zu {@link #hasFill()} prüft die Methode <em>nicht</em>, ob
|
||||||
|
* die Füllfarbe transparent ist.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn eine Füllfarbe gesetzt ist.
|
||||||
|
*/
|
||||||
|
default boolean hasFillColor() {
|
||||||
|
Paint fill = getFill();
|
||||||
|
return fill instanceof Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt an, ob ein Farbverlauf konfiguriert ist.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn ein Farbverlauf gesetzt ist.
|
||||||
|
*/
|
||||||
|
default boolean hasGradient() {
|
||||||
|
Paint fill = getFill();
|
||||||
|
return fill instanceof MultipleGradientPaint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die aktuelle Füllfarbe der Form zurück.
|
||||||
|
*
|
||||||
|
* @return Die aktuelle Füllfarbe oder {@code null}.
|
||||||
|
*/
|
||||||
|
default Color getFillColor() {
|
||||||
|
if( hasFillColor() ) {
|
||||||
|
return (Color) getFill();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllfarbe auf die angegebene Farbe.
|
||||||
|
*
|
||||||
|
* @param color Die neue Füllfarbe oder {@code null}.
|
||||||
|
* @see Color
|
||||||
|
*/
|
||||||
|
default void setFillColor( Color color ) {
|
||||||
|
setFill(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllfarbe auf die angegebene Farbe und setzt die Transparenz
|
||||||
|
* auf den angegebenen Wert. 0 is komplett durchsichtig und 255 komplett
|
||||||
|
* deckend.
|
||||||
|
*
|
||||||
|
* @param color Die neue Füllfarbe oder {@code null}.
|
||||||
|
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||||
|
* @see Color#Color(Color, int)
|
||||||
|
*/
|
||||||
|
default void setFillColor( Color color, int alpha ) {
|
||||||
|
setFillColor(new Color(color, alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllfarbe auf einen Grauwert mit der angegebenen Intensität. 0
|
||||||
|
* entspricht schwarz, 255 entspricht weiß.
|
||||||
|
*
|
||||||
|
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||||
|
* @see Color#Color(int)
|
||||||
|
*/
|
||||||
|
default void setFillColor( int gray ) {
|
||||||
|
setFillColor(gray, gray, gray, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllfarbe auf einen Grauwert mit der angegebenen Intensität und
|
||||||
|
* dem angegebenen Transparenzwert. Der Grauwert 0 entspricht schwarz, 255
|
||||||
|
* entspricht weiß.
|
||||||
|
*
|
||||||
|
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||||
|
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||||
|
* @see Color#Color(int, int)
|
||||||
|
*/
|
||||||
|
default void setFillColor( int gray, int alpha ) {
|
||||||
|
setFillColor(gray, gray, gray, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllfarbe auf die Farbe mit den angegebenen Rot-, Grün- und
|
||||||
|
* Blauanteilen.
|
||||||
|
*
|
||||||
|
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
||||||
|
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
||||||
|
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
||||||
|
* @see Color#Color(int, int, int)
|
||||||
|
* @see <a
|
||||||
|
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||||
|
*/
|
||||||
|
default void setFillColor( int red, int green, int blue ) {
|
||||||
|
setFillColor(red, green, blue, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllfarbe auf die Farbe mit den angegebenen Rot-, Grün- und
|
||||||
|
* Blauanteilen und dem angegebenen Transparenzwert.
|
||||||
|
*
|
||||||
|
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
||||||
|
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
||||||
|
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
||||||
|
* @param alpha Ein Transparenzwert zwischen 0 und 25
|
||||||
|
* @see Color#Color(int, int, int, int)
|
||||||
|
* @see <a
|
||||||
|
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||||
|
*/
|
||||||
|
default void setFillColor( int red, int green, int blue, int alpha ) {
|
||||||
|
setFillColor(new Color(red, green, blue, alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt die Füllung der Form.
|
||||||
|
*/
|
||||||
|
default void noFill() {
|
||||||
|
setFillColor(null);
|
||||||
|
noGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllfarbe auf den Standardwert zurück.
|
||||||
|
*
|
||||||
|
* @see schule.ngb.zm.Constants#DEFAULT_FILLCOLOR
|
||||||
|
*/
|
||||||
|
default void resetFill() {
|
||||||
|
setFillColor(Constants.DEFAULT_FILLCOLOR);
|
||||||
|
noGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt den aktuellen Farbverlauf der Form zurück.
|
||||||
|
*
|
||||||
|
* @return Der aktuelle Farbverlauf oder {@code null}.
|
||||||
|
*/
|
||||||
|
default MultipleGradientPaint getGradient() {
|
||||||
|
if( hasGradient() ) {
|
||||||
|
return (MultipleGradientPaint) getFill();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllung auf einen linearen Farbverlauf, der am Punkt
|
||||||
|
* ({@code fromX}, {@code fromY}) mit der Farbe {@code from} startet und am
|
||||||
|
* Punkt (({@code toX}, {@code toY}) mit der Farbe {@code to} endet.
|
||||||
|
*
|
||||||
|
* @param fromX x-Koordinate des Startpunktes.
|
||||||
|
* @param fromY y-Koordinate des Startpunktes.
|
||||||
|
* @param from Farbe am Startpunkt.
|
||||||
|
* @param toX x-Koordinate des Endpunktes.
|
||||||
|
* @param toY y-Koordinate des Endpunktes.
|
||||||
|
* @param to Farbe am Endpunkt.
|
||||||
|
*/
|
||||||
|
default void setGradient( double fromX, double fromY, Color from, double toX, double toY, Color to ) {
|
||||||
|
setFill(new LinearGradientPaint(
|
||||||
|
(float) fromX, (float) fromY,
|
||||||
|
(float) toX, (float) toY,
|
||||||
|
new float[]{0f, 1f},
|
||||||
|
new java.awt.Color[]{from.getJavaColor(), to.getJavaColor()}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllung auf einen linearen Farbverlauf, der in die angegebene
|
||||||
|
* Richtung verläuft.
|
||||||
|
*
|
||||||
|
* @param from Farbe am Startpunkt.
|
||||||
|
* @param to Farbe am Endpunkt.
|
||||||
|
* @param dir Richtung des Farbverlaufs.
|
||||||
|
*/
|
||||||
|
default void setGradient( Color from, Color to, Options.Direction dir ) {
|
||||||
|
int whalf = (Constants.canvasWidth / 2);
|
||||||
|
int hhalf = (Constants.canvasHeight / 2);
|
||||||
|
setGradient(whalf - dir.x * whalf, hhalf - dir.y * hhalf, from, whalf + dir.x * whalf, hhalf + dir.y * hhalf, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllung auf einen kreisförmigen (radialen) Farbverlauf, mit dem
|
||||||
|
* Zentrum im Punkt ({@code centerX}, {@code centerY}) und dem angegebenen
|
||||||
|
* Radius. Der Verlauf starte im Zentrum mit der Farbe {@code from} und
|
||||||
|
* endet am Rand des durch den Radius beschriebenen Kreises mit der Farbe
|
||||||
|
* {@code to}.
|
||||||
|
*
|
||||||
|
* @param centerX x-Koordinate des Kreismittelpunktes.
|
||||||
|
* @param centerY y-Koordinate des Kreismittelpunktes.
|
||||||
|
* @param radius Radius des Kreises.
|
||||||
|
* @param from Farbe im Zentrum des Kreises.
|
||||||
|
* @param to Farbe am Rand des Kreises.
|
||||||
|
*/
|
||||||
|
default void setGradient( double centerX, double centerY, double radius, Color from, Color to ) {
|
||||||
|
setFill(new RadialGradientPaint(
|
||||||
|
(float) centerX, (float) centerY, (float) radius,
|
||||||
|
new float[]{0f, 1f},
|
||||||
|
new java.awt.Color[]{from.getJavaColor(), to.getJavaColor()}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllung auf einen kreisförmigen (radialen) Farbverlauf, der im
|
||||||
|
* Zentrum beginnt.
|
||||||
|
*
|
||||||
|
* @param from Farbe im Zentrum.
|
||||||
|
* @param to Farbe am Rand.
|
||||||
|
*/
|
||||||
|
default void setGradient( Color from, Color to ) {
|
||||||
|
int whalf = (Constants.canvasWidth / 2);
|
||||||
|
int hhalf = (Constants.canvasHeight / 2);
|
||||||
|
setGradient(whalf, hhalf, Math.min(whalf, hhalf), from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt den Farbverlauf von der Form.
|
||||||
|
*/
|
||||||
|
default void noGradient() {
|
||||||
|
setFill(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -12,10 +12,10 @@ import java.awt.image.BufferedImage;
|
|||||||
* Die {@code Zeichenleinwand} besteht aus einer Reihe von Ebenen, die
|
* Die {@code Zeichenleinwand} besteht aus einer Reihe von Ebenen, die
|
||||||
* übereinandergelegt und von "unten" nach "oben" gezeichnet werden. Die Inhalte
|
* übereinandergelegt und von "unten" nach "oben" gezeichnet werden. Die Inhalte
|
||||||
* der oberen Ebenen können also Inhalte der darunterliegenden verdecken.
|
* der oberen Ebenen können also Inhalte der darunterliegenden verdecken.
|
||||||
*
|
* <p>
|
||||||
* Ebenen sind ein zentraler Bestandteil bei der Implementierung einer {@link Zeichenmaschine}.
|
* Ebenen sind ein zentraler Bestandteil bei der Implementierung einer
|
||||||
* Es werden
|
* {@link Zeichenmaschine}. Sie erben von {@code Constants}, da neue Ebenentypen
|
||||||
* Sie erben von {@code Constants}, damit sie beim
|
* von Nutzern implementiert werden können.
|
||||||
*/
|
*/
|
||||||
public abstract class Layer extends Constants implements Drawable, Updatable {
|
public abstract class Layer extends Constants implements Drawable, Updatable {
|
||||||
|
|
||||||
@@ -43,24 +43,39 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
|||||||
protected boolean active = true;
|
protected boolean active = true;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt eine neue Ebene mit den Standardmaßen.
|
||||||
|
*/
|
||||||
public Layer() {
|
public Layer() {
|
||||||
this(DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
this(DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt eine neue Ebene mit den angegebenen Maßen.
|
||||||
|
*
|
||||||
|
* @param width Die Breite der Ebene.
|
||||||
|
* @param height Die Höhe der Ebene.
|
||||||
|
*/
|
||||||
public Layer( int width, int height ) {
|
public Layer( int width, int height ) {
|
||||||
createCanvas(width, height);
|
createCanvas(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Die Breite der Ebene.
|
||||||
|
*/
|
||||||
public int getWidth() {
|
public int getWidth() {
|
||||||
return buffer.getWidth();
|
return buffer.getWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Die Höhe der Ebene.
|
||||||
|
*/
|
||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
return buffer.getHeight();
|
return buffer.getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ändert die Größe der Ebene auf die angegebene Größe.
|
* Ändert die Größe der Ebene auf die angegebenen Maße.
|
||||||
*
|
*
|
||||||
* @param width Die neue Breite.
|
* @param width Die neue Breite.
|
||||||
* @param height Die neue Höhe.
|
* @param height Die neue Höhe.
|
||||||
@@ -73,11 +88,15 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
|||||||
} else {
|
} else {
|
||||||
createCanvas(width, height);
|
createCanvas(width, height);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: prevent access to graphics context?
|
||||||
|
public Graphics2D getGraphics() {
|
||||||
|
return this.drawing;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die Resourcen der Ebene frei.
|
* Gibt die Ressourcen der Ebene frei.
|
||||||
*/
|
*/
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
drawing.dispose();
|
drawing.dispose();
|
||||||
@@ -88,7 +107,7 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
|||||||
/**
|
/**
|
||||||
* Erstellt einen neuen Puffer für die Ebene und konfiguriert diesen.
|
* Erstellt einen neuen Puffer für die Ebene und konfiguriert diesen.
|
||||||
*
|
*
|
||||||
* @param width Width des neuen Puffers.
|
* @param width Breite des neuen Puffers.
|
||||||
* @param height Höhe des neuen Puffers.
|
* @param height Höhe des neuen Puffers.
|
||||||
*/
|
*/
|
||||||
private void createCanvas( int width, int height ) {
|
private void createCanvas( int width, int height ) {
|
||||||
@@ -115,7 +134,7 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
|||||||
* Erstellt einen neuen Puffer für die Ebene mit der angegebenen Größe und
|
* Erstellt einen neuen Puffer für die Ebene mit der angegebenen Größe und
|
||||||
* kopiert den Inhalt des alten Puffers in den Neuen.
|
* kopiert den Inhalt des alten Puffers in den Neuen.
|
||||||
*
|
*
|
||||||
* @param width Width des neuen Puffers.
|
* @param width Breite des neuen Puffers.
|
||||||
* @param height Höhe des neuen Puffers.
|
* @param height Höhe des neuen Puffers.
|
||||||
*/
|
*/
|
||||||
private void recreateCanvas( int width, int height ) {
|
private void recreateCanvas( int width, int height ) {
|
||||||
@@ -139,14 +158,14 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zeichnet den Puffer auf die Grafik-Instanz.
|
* Zeichnet den Puffer auf den Grafikkontext.
|
||||||
*
|
*
|
||||||
* @param pGraphics
|
* @param graphics Der Grafikkontext, auf den gezeichnet wird.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void draw( Graphics2D pGraphics ) {
|
public void draw( Graphics2D graphics ) {
|
||||||
if( visible ) {
|
if( visible ) {
|
||||||
pGraphics.drawImage(buffer, 0, 0, null);
|
graphics.drawImage(buffer, 0, 0, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,14 +174,26 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
|||||||
return visible;
|
return visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Versteckt die Ebene.
|
||||||
|
*/
|
||||||
public void hide() {
|
public void hide() {
|
||||||
visible = false;
|
visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void view() {
|
/**
|
||||||
|
* Zeigt die Ebene an, falls sie versteckt war.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
|
public void show() {
|
||||||
visible = true;
|
visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Versteckt oder zeigt die Ebene, je nachdem, welchen Zustand sie derzeit
|
||||||
|
* hat.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public void toggle() {
|
public void toggle() {
|
||||||
visible = !visible;
|
visible = !visible;
|
||||||
}
|
}
|
||||||
@@ -176,4 +207,22 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
|||||||
return active;
|
return active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft, ob die angegebenen Koordinaten innerhalb der Ebene liegen, oder
|
||||||
|
* nicht.
|
||||||
|
* <p>
|
||||||
|
* Eine Koordinate liegt in der Ebene, wenn die {@code x}- und
|
||||||
|
* {@code y}-Koordinaten größer oder gleich Null und kleiner als die Breite
|
||||||
|
* bzw. Höhe der Ebene sind.
|
||||||
|
*
|
||||||
|
* @param x Die x-Koordinate.
|
||||||
|
* @param y Die y-Koordinate.
|
||||||
|
* @return {@code true}, wenn die Koordinaten innerhalb der Ebene liegen,
|
||||||
|
* {@code false}, wenn sie außerhalb liegen.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
|
public boolean isInBounds( int x, int y ) {
|
||||||
|
return (x >= 0 && y >= 0 && x < getWidth() && y < getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,17 +11,71 @@ public final class Options {
|
|||||||
private Options() {
|
private Options() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linienstile für Konturlinien.
|
||||||
|
*/
|
||||||
public enum StrokeType {
|
public enum StrokeType {
|
||||||
SOLID, DASHED, DOTTED
|
/**
|
||||||
|
* Durchgezogene Linien.
|
||||||
|
*/
|
||||||
|
SOLID,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getrichelte Linien.
|
||||||
|
*/
|
||||||
|
DASHED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gepunktete Linien.
|
||||||
|
*/
|
||||||
|
DOTTED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stile für Pfeilspitzen.
|
||||||
|
*/
|
||||||
public enum ArrowHead {
|
public enum ArrowHead {
|
||||||
LINES, FILLED
|
/**
|
||||||
|
* Einfache Pfeilspitze aus zwei Linien.
|
||||||
|
*/
|
||||||
|
LINES,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gefülltes Dreieck.
|
||||||
|
*/
|
||||||
|
FILLED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arten von Bögen.
|
||||||
|
* <p>
|
||||||
|
* Die Werte legen fest, wie Bögen geschlossen werden sollen, wenn sie
|
||||||
|
* beispielsweise gefüllt werden.
|
||||||
|
* <p>
|
||||||
|
* Wrapper für die AWT-Konstanten in {@link Arc2D}.
|
||||||
|
*/
|
||||||
public enum PathType {
|
public enum PathType {
|
||||||
OPEN(Arc2D.OPEN), CLOSED(Arc2D.CHORD), PIE(Arc2D.PIE);
|
/**
|
||||||
|
* Offener Pfad, bei dem die Pfadenden direkt miteinander verbunden
|
||||||
|
* werden ohne eine Linie zu ziehen.
|
||||||
|
*/
|
||||||
|
OPEN(Arc2D.OPEN),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Geschlossener Pfad, bei dem die Pfadenden direkt miteinander
|
||||||
|
* verbunden werden, indem eine Linie gezogen wird.
|
||||||
|
*/
|
||||||
|
CLOSED(Arc2D.CHORD),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Geschlossener Pfad, bei dem Linien von den Pfadenden zum Mittelpunkt
|
||||||
|
* des Kreises, der den Kreisbogen festlegt, gezogen werden.
|
||||||
|
*/
|
||||||
|
PIE(Arc2D.PIE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der entsprechende Wert der Konstanten in {@link Arc2D}
|
||||||
|
*/
|
||||||
public final int awt_type;
|
public final int awt_type;
|
||||||
|
|
||||||
PathType( int type ) {
|
PathType( int type ) {
|
||||||
@@ -33,24 +87,100 @@ public final class Options {
|
|||||||
* Zustände in denen sich die Zeichenmaschine befinden kann.
|
* Zustände in denen sich die Zeichenmaschine befinden kann.
|
||||||
*/
|
*/
|
||||||
public enum AppState {
|
public enum AppState {
|
||||||
|
/**
|
||||||
|
* Die Zeichenmaschine befindet sich in der Initialisierung. Die
|
||||||
|
* Laufzeitumgebung wird konfiguriert und alle nötigen Komonenten
|
||||||
|
* ({@link Zeichenfenster}, {@link Zeichenleinwand}, ...) werden
|
||||||
|
* erstellt.
|
||||||
|
*/
|
||||||
INITIALIZING,
|
INITIALIZING,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Initialisierung der Zeichenmaschine ist beendet, aber der
|
||||||
|
* {@link schule.ngb.zm.Zeichenmaschine.Zeichenthread Zeichenthread}
|
||||||
|
* wurde noch nicht gestartet.
|
||||||
|
*/
|
||||||
INITIALIZED,
|
INITIALIZED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Zeichenmaschine wurde gestartet und der
|
||||||
|
* {@link schule.ngb.zm.Zeichenmaschine.Zeichenthread Zeichenthread}
|
||||||
|
* arbeitet.
|
||||||
|
*/
|
||||||
RUNNING,
|
RUNNING,
|
||||||
UPDATING,
|
|
||||||
DRAWING,
|
/**
|
||||||
|
* Der {@link schule.ngb.zm.Zeichenmaschine.Zeichenthread Zeichenthread}
|
||||||
|
* wurde pausiert.
|
||||||
|
*/
|
||||||
PAUSED,
|
PAUSED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der {@link schule.ngb.zm.Zeichenmaschine.Zeichenthread Zeichenthread}
|
||||||
|
* wurde gestoppt, die Zeichenmaschine ist aber noch nicht vollständig
|
||||||
|
* heruntergefahren und hat noch nicht alle Ressourcen freigegeben.
|
||||||
|
*/
|
||||||
STOPPED,
|
STOPPED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der {@link schule.ngb.zm.Zeichenmaschine.Zeichenthread Zeichenthread}
|
||||||
|
* ist beendet.
|
||||||
|
*/
|
||||||
TERMINATED,
|
TERMINATED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Zeichenmaschine ist dabei, vollständig herunterzufahren und alle
|
||||||
|
* Ressourcen freizugeben.
|
||||||
|
*/
|
||||||
|
QUITING,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der {@link schule.ngb.zm.Zeichenmaschine.Zeichenthread Zeichenthread}
|
||||||
|
* wartet gerade auf den nächsten Frame.
|
||||||
|
*/
|
||||||
IDLE,
|
IDLE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Zeichenmaschine führt gerade
|
||||||
|
* {@link Zeichenmaschine#update(double)} aus.
|
||||||
|
*/
|
||||||
|
UPDATING,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Zeichenmaschine führt gerade {@link Zeichenmaschine#draw()} aus.
|
||||||
|
*/
|
||||||
|
DRAWING,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Ausführung der Zeichenmaschine wurde mit
|
||||||
|
* {@link Zeichenmaschine#delay(int)} verzögert und wartet auf
|
||||||
|
* Fortsetzung.
|
||||||
|
*/
|
||||||
DELAYED,
|
DELAYED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Zeichenmaschine sendet gereade gesammelte Events und führt Tasks
|
||||||
|
* aus.
|
||||||
|
*/
|
||||||
DISPATCHING
|
DISPATCHING
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Richtungen für die Ausrichtung von Formen. Richtungen sind durch
|
* Richtungen für die Ausrichtung von Formen. Richtungen sind durch
|
||||||
* Einheitsvektoren bzw. deren Kombination repräsentiert, wodurch mit ihnen
|
* Einheitsvektoren bzw. deren Kombination im Koordinatensystem der
|
||||||
* gerechnet werden kann. Jede Richtung ist zusätzlich als Himmelsrichtung
|
* Zeichenfläche repräsentiert, wodurch mit ihnen gerechnet werden kann. Die
|
||||||
* definiert.
|
* Richtung {@link #DOWN} ist beispielsweise gleich {@code (1, 0)}.
|
||||||
|
* <p>
|
||||||
|
* Jede Richtung ist zusätzlich als Himmelsrichtung definiert. {@link #EAST}
|
||||||
|
* ist äquivalent zu {@link #RIGHT} als {@code (0, 1)} definiert. Auch wenn
|
||||||
|
* beide Werte dieselbe Richtung beschreiben sind sie nicht "gleich"
|
||||||
|
* ({@code EAST != RIGHT}). Um verschiedene Richtungen zuverlässig zu
|
||||||
|
* vergleichen, sollte daher {@link #equals(Direction)} verwendet werden.
|
||||||
|
* <p>
|
||||||
|
* Für zusammengesetzten Richtungen wie {@link #NORTHEAST} bzw
|
||||||
|
* {@link #UPRIGHT} lassen sich mit {@link #in(Direction)} und
|
||||||
|
* {@link #contains(Direction)} Beziehungen zu den anderen Richtungen
|
||||||
|
* prüfen. Beispielsweise ist {@code NORTHEAST.contains(NORTH)} wahr.
|
||||||
*/
|
*/
|
||||||
public enum Direction {
|
public enum Direction {
|
||||||
CENTER(0, 0),
|
CENTER(0, 0),
|
||||||
@@ -88,31 +218,117 @@ public final class Options {
|
|||||||
this.y = original.y;
|
this.y = original.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft, ob die angegebene Richtung gleich dieser ist. Dabei werden die
|
||||||
|
* Komponenten des Richtungsvektors geprüft. Daher sind für die Methode
|
||||||
|
* beispielsweise {@link #NORTH} und {@link #UP} gleich.
|
||||||
|
*
|
||||||
|
* @param dir Eine andere Richtung.
|
||||||
|
* @return {@code true}, wenn die Richtungen dieselben Komponenten
|
||||||
|
* haben, {@code false} sonst.
|
||||||
|
*/
|
||||||
|
public boolean equals( Direction dir ) {
|
||||||
|
return this.x == dir.x && this.y == dir.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft, ob diese Richtung Tile der angegebenen Richtung ist.
|
||||||
|
* <p>
|
||||||
|
* Beispielsweise ist {@link #NORTH} Teil von {@link #NORTHWEST}, aber
|
||||||
|
* nicht von {@link #SOUTHWEST}. Dabei wird doe Art der Richtung nicht
|
||||||
|
* beachtet. {@link #UP} ist daher auch Teil von {@link #NORTHWEST}.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* NORTH.in(NORTHWEST) // true
|
||||||
|
* NORTH.in(SOUTHWEST) // false
|
||||||
|
* UP.in(NORTHWEST) // true
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param dir Eine andere Richtung.
|
||||||
|
* @return {@code true}, wenn diese Richtungen Teil der anderen ist,
|
||||||
|
* {@code false} sonst.
|
||||||
|
*/
|
||||||
public boolean in( Direction dir ) {
|
public boolean in( Direction dir ) {
|
||||||
return (this.x == dir.x && this.y == 0) || (this.y == dir.y && this.x == 0) || (this.x == dir.x && this.y == dir.y);
|
return (this.x == dir.x && this.y == 0) || (this.y == dir.y && this.x == 0) || (this.x == dir.x && this.y == dir.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft, ob die angegebene Richtung Teil dieser Richtung ist.
|
||||||
|
* <p>
|
||||||
|
* Beispielsweise ist {@link #NORTH} Teil von {@link #NORTHWEST}, aber
|
||||||
|
* nicht von {@link #SOUTHWEST}. Dabei wird die Art der Richtung nicht
|
||||||
|
* beachtet. {@link #UP} ist daher auch Teil von {@link #NORTHWEST}.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* NORTHWEST.contains(NORTH) // true
|
||||||
|
* SOUTHWEST.in(NORTH) // false
|
||||||
|
* NORTHWEST.in(UP) // true
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param dir Eine andere Richtung.
|
||||||
|
* @return {@code true}, wenn diese Richtungen Teil der anderen ist,
|
||||||
|
* {@code false} sonst.
|
||||||
|
*/
|
||||||
public boolean contains( Direction dir ) {
|
public boolean contains( Direction dir ) {
|
||||||
return dir.in(this);
|
return dir.in(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Diese Richtung als Vektor-Objekt.
|
||||||
|
*/
|
||||||
public Vector asVector() {
|
public Vector asVector() {
|
||||||
return new Vector(x, y);
|
return new Vector(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die entgegengesetzte Richtung zu dieser zurück.
|
* Liefert die entgegengesetzte Richtung zu dieser.
|
||||||
|
* <p>
|
||||||
|
* Es wird die Art der Richtung berücksichtigt. Das bedeutet, das
|
||||||
|
* Inverse von {@link #UP} ist {@link #DOWN}, während das Inverse von
|
||||||
|
* {@link #NORTH} zu {@link #SOUTH} wird.
|
||||||
*
|
*
|
||||||
* @return
|
* @return Die entgegengesetzte Richtung zu dieser.
|
||||||
*/
|
*/
|
||||||
public Direction inverse() {
|
public Direction inverse() {
|
||||||
for( Direction dir : Direction.values() ) {
|
switch( this ) {
|
||||||
if( dir.x == -this.x && dir.y == -this.y ) {
|
case UP:
|
||||||
return dir;
|
return DOWN;
|
||||||
}
|
case DOWN:
|
||||||
}
|
return UP;
|
||||||
|
case LEFT:
|
||||||
|
return RIGHT;
|
||||||
|
case RIGHT:
|
||||||
|
return LEFT;
|
||||||
|
case UPLEFT:
|
||||||
|
return DOWNRIGHT;
|
||||||
|
case UPRIGHT:
|
||||||
|
return DOWNLEFT;
|
||||||
|
case DOWNLEFT:
|
||||||
|
return UPRIGHT;
|
||||||
|
case DOWNRIGHT:
|
||||||
|
return UPLEFT;
|
||||||
|
case MIDDLE:
|
||||||
|
return MIDDLE;
|
||||||
|
case NORTH:
|
||||||
|
return SOUTH;
|
||||||
|
case SOUTH:
|
||||||
|
return NORTH;
|
||||||
|
case EAST:
|
||||||
|
return WEST;
|
||||||
|
case WEST:
|
||||||
|
return EAST;
|
||||||
|
case SOUTHWEST:
|
||||||
|
return NORTHEAST;
|
||||||
|
case SOUTHEAST:
|
||||||
|
return NORTHWEST;
|
||||||
|
case NORTHEAST:
|
||||||
|
return SOUTHWEST;
|
||||||
|
case NORTHWEST:
|
||||||
|
return SOUTHEAST;
|
||||||
|
default:
|
||||||
return CENTER;
|
return CENTER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public class Spielemaschine extends Zeichenmaschine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw( Graphics2D pGraphics ) {
|
public void draw( Graphics2D graphics ) {
|
||||||
clear();
|
clear();
|
||||||
List<Drawable> it = List.copyOf(drawables);
|
List<Drawable> it = List.copyOf(drawables);
|
||||||
for( Drawable d: it ) {
|
for( Drawable d: it ) {
|
||||||
@@ -116,7 +116,7 @@ public class Spielemaschine extends Zeichenmaschine {
|
|||||||
d.draw(drawing);
|
d.draw(drawing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.draw(pGraphics);
|
super.draw(graphics);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +1,68 @@
|
|||||||
package schule.ngb.zm.shapes;
|
package schule.ngb.zm;
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
import schule.ngb.zm.shapes.Shape;
|
||||||
import schule.ngb.zm.Constants;
|
|
||||||
import schule.ngb.zm.Drawable;
|
|
||||||
import schule.ngb.zm.Options;
|
|
||||||
|
|
||||||
import java.awt.BasicStroke;
|
import java.awt.BasicStroke;
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.Stroke;
|
import java.awt.Stroke;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Drawable} Klassen, die mit einer Konturlinie versehen werden können.
|
||||||
|
* <p>
|
||||||
|
* Das {@code Strokeable} Interface dient hauptsächlich zur Vereinheitlichung
|
||||||
|
* der API für Konturlinien. Durch Implementation wird sichergestellt, dass alle
|
||||||
|
* Objekte, die eine Konturlinie haben können, dieselben Methoden zur Verfügung
|
||||||
|
* stellen. Wenn eine {@link Shape} eine
|
||||||
|
* {@link Strokeable#setStrokeColor(Color, int)} Methode hat, dann sollte auch
|
||||||
|
* eine {@link schule.ngb.zm.layers.TurtleLayer.Turtle} dieselbe Methode
|
||||||
|
* anbieten. Im Einzelfall kann es sinnvoll sein, weitere Methoden für
|
||||||
|
* Konturlinien zur verfügung zu stellen. Allerdings sollte davon nach
|
||||||
|
* Möglichkeit zugunsten einer einheitlichen API abgesehen werden.
|
||||||
|
* <p>
|
||||||
|
* Das Äquivalent für Füllungen stellt {@link Fillable} dar.
|
||||||
|
*/
|
||||||
|
public interface Strokeable extends Drawable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basisklasse für Formen, die eine Konturlinie besitzen.
|
* Setzt den {@code Stroke} für die Konturlinie direkt.
|
||||||
|
*
|
||||||
|
* @param stroke Ein {@code Stroke}-Objekt.
|
||||||
*/
|
*/
|
||||||
public abstract class StrokedShape extends Constants implements Drawable {
|
void setStroke( Stroke stroke );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aktuelle Farbe der Konturlinie oder {@code null}, wenn die Form ohne
|
* Gibt ein {@code Stroke}-Objekt mit den aktuell gesetzten Eigenschaften
|
||||||
* kontur dargestellt werden soll.
|
* zurück.
|
||||||
|
*
|
||||||
|
* @return Ein {@code Stroke} mit den passenden Kontureigenschaften.
|
||||||
*/
|
*/
|
||||||
protected Color strokeColor = DEFAULT_STROKECOLOR;
|
Stroke getStroke();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Dicke der Konturlinie. Wird nicht kleiner als 0.
|
* Gibt an, ob die aktuell gesetzten Eigenschaften eine sichtbare
|
||||||
|
* Konturlinie erzeugen.
|
||||||
|
* <p>
|
||||||
|
* Die Konturlinie gilt als sichtbar, wenn sie eine nicht transparente Farbe
|
||||||
|
* und eine Dicke größer 0 besitzt.
|
||||||
|
* <p>
|
||||||
|
* Das bedeutet, falls die Methode {@code false} zurückgibt, dann kann
|
||||||
|
* {@link #getStroke()} trotzdem ein gültiges {@link Stroke}-Objekt
|
||||||
|
* zurückgeben, beispielsweise wenn keine Farbe gesetzt wurde.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn die Konturlinie sichtbar ist, {@code false}
|
||||||
|
* sonst.
|
||||||
*/
|
*/
|
||||||
protected double strokeWeight = DEFAULT_STROKEWEIGHT;
|
default boolean hasStroke() {
|
||||||
|
Color strokeColor = getStrokeColor();
|
||||||
/**
|
double strokeWeight = getStrokeWeight();
|
||||||
* Die Art der Konturlinie.
|
return strokeColor != null && strokeColor.getAlpha() > 0 && strokeWeight > 0;
|
||||||
*/
|
}
|
||||||
protected Options.StrokeType strokeType = SOLID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache für den aktuellen {@code Stroke} der Kontur. Wird nach Änderung
|
|
||||||
* einer der Kontureigenschaften auf {@code null} gesetzt und beim nächsten
|
|
||||||
* Zeichnen neu erstellt.
|
|
||||||
*/
|
|
||||||
protected Stroke stroke = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die aktuelle Farbe der Konturlinie zurück.
|
* Gibt die aktuelle Farbe der Konturlinie zurück.
|
||||||
*
|
*
|
||||||
* @return Die Konturfarbe oder {@code null}.
|
* @return Die Konturfarbe oder {@code null}.
|
||||||
*/
|
*/
|
||||||
public Color getStrokeColor() {
|
Color getStrokeColor();
|
||||||
return strokeColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt die Farbe der Konturlinie auf die angegebene Farbe.
|
* Setzt die Farbe der Konturlinie auf die angegebene Farbe.
|
||||||
@@ -53,9 +70,7 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
|||||||
* @param color Die neue Farbe der Konturlinie.
|
* @param color Die neue Farbe der Konturlinie.
|
||||||
* @see Color
|
* @see Color
|
||||||
*/
|
*/
|
||||||
public void setStrokeColor( Color color ) {
|
void setStrokeColor( Color color );
|
||||||
this.strokeColor = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt die Farbe der Konturlinie auf die angegebene Farbe und setzt die
|
* Setzt die Farbe der Konturlinie auf die angegebene Farbe und setzt die
|
||||||
@@ -66,7 +81,7 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
|||||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||||
* @see Color#Color(Color, int)
|
* @see Color#Color(Color, int)
|
||||||
*/
|
*/
|
||||||
public void setStrokeColor( Color color, int alpha ) {
|
default void setStrokeColor( Color color, int alpha ) {
|
||||||
setStrokeColor(new Color(color, alpha));
|
setStrokeColor(new Color(color, alpha));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +92,7 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
|||||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||||
* @see Color#Color(int)
|
* @see Color#Color(int)
|
||||||
*/
|
*/
|
||||||
public void setStrokeColor( int gray ) {
|
default void setStrokeColor( int gray ) {
|
||||||
setStrokeColor(gray, gray, gray, 255);
|
setStrokeColor(gray, gray, gray, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +105,7 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
|||||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||||
* @see Color#Color(int, int)
|
* @see Color#Color(int, int)
|
||||||
*/
|
*/
|
||||||
public void setStrokeColor( int gray, int alpha ) {
|
default void setStrokeColor( int gray, int alpha ) {
|
||||||
setStrokeColor(gray, gray, gray, alpha);
|
setStrokeColor(gray, gray, gray, alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +120,7 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
|||||||
* @see <a
|
* @see <a
|
||||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||||
*/
|
*/
|
||||||
public void setStrokeColor( int red, int green, int blue ) {
|
default void setStrokeColor( int red, int green, int blue ) {
|
||||||
setStrokeColor(red, green, blue, 255);
|
setStrokeColor(red, green, blue, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,14 +136,14 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
|||||||
* @see <a
|
* @see <a
|
||||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||||
*/
|
*/
|
||||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
default void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||||
setStrokeColor(new Color(red, green, blue, alpha));
|
setStrokeColor(new Color(red, green, blue, alpha));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entfernt die Kontur der Form.
|
* Entfernt die Kontur der Form.
|
||||||
*/
|
*/
|
||||||
public void noStroke() {
|
default void noStroke() {
|
||||||
setStrokeColor(null);
|
setStrokeColor(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,10 +154,10 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
|||||||
* @see schule.ngb.zm.Constants#DEFAULT_STROKEWEIGHT
|
* @see schule.ngb.zm.Constants#DEFAULT_STROKEWEIGHT
|
||||||
* @see schule.ngb.zm.Constants#SOLID
|
* @see schule.ngb.zm.Constants#SOLID
|
||||||
*/
|
*/
|
||||||
public void resetStroke() {
|
default void resetStroke() {
|
||||||
setStrokeColor(DEFAULT_STROKECOLOR);
|
setStrokeColor(Constants.DEFAULT_STROKECOLOR);
|
||||||
setStrokeWeight(DEFAULT_STROKEWEIGHT);
|
setStrokeWeight(Constants.DEFAULT_STROKEWEIGHT);
|
||||||
setStrokeType(SOLID);
|
setStrokeType(Constants.SOLID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -150,9 +165,7 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
|||||||
*
|
*
|
||||||
* @return Die aktuelle Dicke der Linie.
|
* @return Die aktuelle Dicke der Linie.
|
||||||
*/
|
*/
|
||||||
public double getStrokeWeight() {
|
double getStrokeWeight();
|
||||||
return strokeWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt die Dicke der Konturlinie. Die Dicke muss größer 0 sein. Wird 0
|
* Setzt die Dicke der Konturlinie. Die Dicke muss größer 0 sein. Wird 0
|
||||||
@@ -160,9 +173,8 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
|||||||
*
|
*
|
||||||
* @param weight Die Dicke der Konturlinie.
|
* @param weight Die Dicke der Konturlinie.
|
||||||
*/
|
*/
|
||||||
public void setStrokeWeight( double weight ) {
|
default void setStrokeWeight( double weight ) {
|
||||||
this.strokeWeight = max(0.0, weight);
|
setStroke(createStroke(getStrokeType(), weight));
|
||||||
this.stroke = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -171,78 +183,48 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
|||||||
* @return Die aktuelle Art der Konturlinie.
|
* @return Die aktuelle Art der Konturlinie.
|
||||||
* @see Options.StrokeType
|
* @see Options.StrokeType
|
||||||
*/
|
*/
|
||||||
public Options.StrokeType getStrokeType() {
|
Options.StrokeType getStrokeType();
|
||||||
return strokeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link #DASHED},
|
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link Constants#DASHED},
|
||||||
* {@link #DOTTED} und {@link #SOLID}.
|
* {@link Constants#DOTTED} und {@link Constants#SOLID}.
|
||||||
*
|
*
|
||||||
* @param type Eine der möglichen Konturarten.
|
* @param type Eine der möglichen Konturarten.
|
||||||
* @see Options.StrokeType
|
* @see Options.StrokeType
|
||||||
*/
|
*/
|
||||||
public void setStrokeType( Options.StrokeType type ) {
|
default void setStrokeType( Options.StrokeType type ) {
|
||||||
this.strokeType = type;
|
setStroke(createStroke(type, getStrokeWeight()));
|
||||||
this.stroke = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public abstract void draw( Graphics2D graphics );
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hilfsmethode, um ein {@link Stroke} Objekt mit den aktuellen
|
* Hilfsmethode, um ein {@link Stroke} Objekt mit den aktuellen
|
||||||
* Kontureigenschaften zu erstellen. Der aktuelle {@code Stroke} wird
|
* Kontureigenschaften zu erstellen. Der aktuelle {@code Stroke} wird
|
||||||
* zwischengespeichert.
|
* zwischengespeichert.
|
||||||
*
|
*
|
||||||
|
* @param strokeType
|
||||||
|
* @param strokeWeight
|
||||||
* @return Ein {@code Stroke} mit den passenden Kontureigenschaften.
|
* @return Ein {@code Stroke} mit den passenden Kontureigenschaften.
|
||||||
*/
|
*/
|
||||||
protected Stroke createStroke() {
|
static Stroke createStroke( Options.StrokeType strokeType, double strokeWeight ) {
|
||||||
// TODO: Used global cached Stroke Objects?
|
|
||||||
if( stroke == null ) {
|
|
||||||
switch( strokeType ) {
|
switch( strokeType ) {
|
||||||
case DOTTED:
|
case DOTTED:
|
||||||
stroke = new BasicStroke(
|
return new BasicStroke(
|
||||||
(float) strokeWeight,
|
(float) strokeWeight,
|
||||||
BasicStroke.CAP_ROUND,
|
BasicStroke.CAP_ROUND,
|
||||||
BasicStroke.JOIN_ROUND,
|
BasicStroke.JOIN_ROUND,
|
||||||
10.0f, new float[]{1.0f, 5.0f}, 0.0f);
|
10.0f, new float[]{1.0f, 5.0f}, 0.0f);
|
||||||
break;
|
|
||||||
case DASHED:
|
case DASHED:
|
||||||
stroke = new BasicStroke(
|
return new BasicStroke(
|
||||||
(float) strokeWeight,
|
(float) strokeWeight,
|
||||||
BasicStroke.CAP_ROUND,
|
BasicStroke.CAP_ROUND,
|
||||||
BasicStroke.JOIN_ROUND,
|
BasicStroke.JOIN_ROUND,
|
||||||
10.0f, new float[]{5.0f}, 0.0f);
|
10.0f, new float[]{5.0f}, 0.0f);
|
||||||
break;
|
|
||||||
case SOLID:
|
case SOLID:
|
||||||
default:
|
default:
|
||||||
stroke = new BasicStroke(
|
return new BasicStroke(
|
||||||
(float) strokeWeight,
|
(float) strokeWeight,
|
||||||
BasicStroke.CAP_ROUND,
|
BasicStroke.CAP_ROUND,
|
||||||
BasicStroke.JOIN_ROUND);
|
BasicStroke.JOIN_ROUND);
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stroke;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hilfsmethode für Unterklassen, um die angegebene Form mit den aktuellen
|
|
||||||
* Kontureigenschaften auf den Grafik-Kontext zu zeichnen. Die Methode
|
|
||||||
* verändert gegebenenfalls die aktuelle Farbe des Grafikobjekts und setzt
|
|
||||||
* sie nicht auf den Ursprungswert zurück, wie von {@link #draw(Graphics2D)}
|
|
||||||
* gefordert. Dies sollte die aufrufende Unterklasse übernehmen.
|
|
||||||
*
|
|
||||||
* @param shape Die zu zeichnende Java-AWT Form
|
|
||||||
* @param graphics Das Grafikobjekt.
|
|
||||||
*/
|
|
||||||
protected void strokeShape( java.awt.Shape shape, Graphics2D graphics ) {
|
|
||||||
if( strokeColor != null && strokeColor.getAlpha() > 0
|
|
||||||
&& strokeWeight > 0.0 ) {
|
|
||||||
graphics.setColor(strokeColor.getJavaColor());
|
|
||||||
graphics.setStroke(createStroke());
|
|
||||||
graphics.draw(shape);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ public interface Updatable {
|
|||||||
* @return {@code true}, wenn das Objekt aktiv ist, {@code false}
|
* @return {@code true}, wenn das Objekt aktiv ist, {@code false}
|
||||||
* andernfalls.
|
* andernfalls.
|
||||||
*/
|
*/
|
||||||
public boolean isActive();
|
boolean isActive();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Änderung des Zustandes des Objekts abhängig vom Zeitintervall
|
* Änderung des Zustandes des Objekts abhängig vom Zeitintervall
|
||||||
@@ -39,6 +39,6 @@ public interface Updatable {
|
|||||||
*
|
*
|
||||||
* @param delta Zeitintervall seit dem letzten Aufruf (in Sekunden).
|
* @param delta Zeitintervall seit dem letzten Aufruf (in Sekunden).
|
||||||
*/
|
*/
|
||||||
public void update( double delta );
|
void update( double delta );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import java.awt.geom.Point2D;
|
|||||||
* Der Vektor der Zeichenmaschine erweitert die Klasse {@link Point2D} und lässt
|
* Der Vektor der Zeichenmaschine erweitert die Klasse {@link Point2D} und lässt
|
||||||
* sich dadurch einfach mit den Klassen des {@link java.awt} Pakets benutzen.
|
* sich dadurch einfach mit den Klassen des {@link java.awt} Pakets benutzen.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public class Vector extends Point2D.Double {
|
public class Vector extends Point2D.Double {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,7 +128,7 @@ public class Vector extends Point2D.Double {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt einen neuen Vektor mit derselben Richtun wie der angegebene
|
* Erzeugt einen neuen Vektor mit derselben Richtung wie der angegebene
|
||||||
* Vektor und der Länge 1.
|
* Vektor und der Länge 1.
|
||||||
*
|
*
|
||||||
* @param vector Der original Vektor.
|
* @param vector Der original Vektor.
|
||||||
@@ -197,6 +198,7 @@ public class Vector extends Point2D.Double {
|
|||||||
* @param y Der neue y-Wert.
|
* @param y Der neue y-Wert.
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector set( double x, double y ) {
|
public Vector set( double x, double y ) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
@@ -210,6 +212,7 @@ public class Vector extends Point2D.Double {
|
|||||||
* @param vector Ein Vektor.
|
* @param vector Ein Vektor.
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector set( Vector vector ) {
|
public Vector set( Vector vector ) {
|
||||||
x = vector.x;
|
x = vector.x;
|
||||||
y = vector.y;
|
y = vector.y;
|
||||||
@@ -223,6 +226,7 @@ public class Vector extends Point2D.Double {
|
|||||||
* @param pPunkt Ein Punkt.
|
* @param pPunkt Ein Punkt.
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector set( Point2D pPunkt ) {
|
public Vector set( Point2D pPunkt ) {
|
||||||
x = pPunkt.getX();
|
x = pPunkt.getX();
|
||||||
x = pPunkt.getY();
|
x = pPunkt.getY();
|
||||||
@@ -271,6 +275,7 @@ public class Vector extends Point2D.Double {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Legt die Länge des Vektors fest.
|
* Legt die Länge des Vektors fest.
|
||||||
|
*
|
||||||
* @param length Die neue Länge des Vektors.
|
* @param length Die neue Länge des Vektors.
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
@@ -294,9 +299,9 @@ public class Vector extends Point2D.Double {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector normalize() {
|
public Vector normalize() {
|
||||||
double len = length();
|
double len = length();
|
||||||
if( len != 0 && len != 1 ) {
|
if( len != 0 && len != 1 ) {
|
||||||
@@ -308,9 +313,11 @@ public class Vector extends Point2D.Double {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Addiert den Vektor {@code vector} zu diesem.
|
* Addiert den Vektor {@code vector} zu diesem.
|
||||||
|
*
|
||||||
* @param vector Ein anderer Vektor.
|
* @param vector Ein anderer Vektor.
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector add( Vector vector ) {
|
public Vector add( Vector vector ) {
|
||||||
x += vector.x;
|
x += vector.x;
|
||||||
y += vector.y;
|
y += vector.y;
|
||||||
@@ -319,10 +326,12 @@ public class Vector extends Point2D.Double {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Addiert die angegebenen Werte zur x- und y-Komponente des Vektors.
|
* Addiert die angegebenen Werte zur x- und y-Komponente des Vektors.
|
||||||
|
*
|
||||||
* @param x Summand x-Komponente.
|
* @param x Summand x-Komponente.
|
||||||
* @param y Summand y-Komponente.
|
* @param y Summand y-Komponente.
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector add( double x, double y ) {
|
public Vector add( double x, double y ) {
|
||||||
this.x += x;
|
this.x += x;
|
||||||
this.y += y;
|
this.y += y;
|
||||||
@@ -342,24 +351,28 @@ public class Vector extends Point2D.Double {
|
|||||||
return vec;
|
return vec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector sub( Vector vector ) {
|
public Vector sub( Vector vector ) {
|
||||||
x -= vector.x;
|
x -= vector.x;
|
||||||
y -= vector.y;
|
y -= vector.y;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector sub( double x, double y ) {
|
public Vector sub( double x, double y ) {
|
||||||
this.x -= x;
|
this.x -= x;
|
||||||
this.y -= y;
|
this.y -= y;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector scale( double scalar ) {
|
public Vector scale( double scalar ) {
|
||||||
x *= scalar;
|
x *= scalar;
|
||||||
y *= scalar;
|
y *= scalar;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector div( double scalar ) {
|
public Vector div( double scalar ) {
|
||||||
if( scalar == 0.0 ) {
|
if( scalar == 0.0 ) {
|
||||||
throw new IllegalArgumentException("Can't divide by zero.");
|
throw new IllegalArgumentException("Can't divide by zero.");
|
||||||
@@ -409,8 +422,8 @@ public class Vector extends Point2D.Double {
|
|||||||
* dem quadrierten Abstand durchführen, wenn auch die gewünschte Entfernung
|
* dem quadrierten Abstand durchführen, wenn auch die gewünschte Entfernung
|
||||||
* quadriert wird.
|
* quadriert wird.
|
||||||
*
|
*
|
||||||
* @param vector
|
* @param vector Ein anderer Vektor.
|
||||||
* @return
|
* @return Das Quadrat der Entfernung zum anderen Vektor.
|
||||||
*/
|
*/
|
||||||
public double distanceSq( Vector vector ) {
|
public double distanceSq( Vector vector ) {
|
||||||
return super.distanceSq(vector);
|
return super.distanceSq(vector);
|
||||||
@@ -487,6 +500,7 @@ public class Vector extends Point2D.Double {
|
|||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
* @see #setLength(double)
|
* @see #setLength(double)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector limit( double max ) {
|
public Vector limit( double max ) {
|
||||||
if( lengthSq() > max * max ) {
|
if( lengthSq() > max * max ) {
|
||||||
setLength(max);
|
setLength(max);
|
||||||
@@ -503,6 +517,7 @@ public class Vector extends Point2D.Double {
|
|||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
* @see #setLength(double)
|
* @see #setLength(double)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector limit( double min, double max ) {
|
public Vector limit( double min, double max ) {
|
||||||
if( min > max ) {
|
if( min > max ) {
|
||||||
double t = min;
|
double t = min;
|
||||||
@@ -556,6 +571,7 @@ public class Vector extends Point2D.Double {
|
|||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
* @see #rotate(double)
|
* @see #rotate(double)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector rotateRad( double rad ) {
|
public Vector rotateRad( double rad ) {
|
||||||
double temp = x;
|
double temp = x;
|
||||||
x = x * Math.cos(rad) - y * Math.sin(rad);
|
x = x * Math.cos(rad) - y * Math.sin(rad);
|
||||||
@@ -587,6 +603,7 @@ public class Vector extends Point2D.Double {
|
|||||||
* @param t Ein Wert zwischen 0 und 1.
|
* @param t Ein Wert zwischen 0 und 1.
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector morph( Vector vector, double t ) {
|
public Vector morph( Vector vector, double t ) {
|
||||||
double tt = Math.min(Math.max(t, 0.0), 1.0);
|
double tt = Math.min(Math.max(t, 0.0), 1.0);
|
||||||
x = x + (vector.x - x) * tt;
|
x = x + (vector.x - x) * tt;
|
||||||
@@ -631,6 +648,7 @@ public class Vector extends Point2D.Double {
|
|||||||
* @param t Ein Wert zwischen 0 und 1.
|
* @param t Ein Wert zwischen 0 und 1.
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector interpolate( Vector vector, double t ) {
|
public Vector interpolate( Vector vector, double t ) {
|
||||||
x = x + (vector.x - x) * t;
|
x = x + (vector.x - x) * t;
|
||||||
y = y + (vector.y - y) * t;
|
y = y + (vector.y - y) * t;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import java.awt.*;
|
|||||||
import java.awt.event.KeyAdapter;
|
import java.awt.event.KeyAdapter;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.awt.event.KeyListener;
|
import java.awt.event.KeyListener;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -24,6 +23,12 @@ import java.util.ArrayList;
|
|||||||
*/
|
*/
|
||||||
public class Zeichenfenster extends JFrame {
|
public class Zeichenfenster extends JFrame {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt das Look and Feel auf den Standard des Systems.
|
||||||
|
* <p>
|
||||||
|
* Sollte einmalig vor erstellen des erstyen Programmfensters aufgerufen
|
||||||
|
* werden.
|
||||||
|
*/
|
||||||
public static final void setLookAndFeel() {
|
public static final void setLookAndFeel() {
|
||||||
try {
|
try {
|
||||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||||
@@ -32,6 +37,17 @@ public class Zeichenfenster extends JFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ermittelt ein {@link GraphicsDevice Anzeigegerät}, auf dem ein neues
|
||||||
|
* Zeichenfenster angezeigt werden soll. In der Regel ist dies der
|
||||||
|
* Bildschirm, auf dem sich derzeit der Mauszeiger befindet. Kann kein
|
||||||
|
* solcher Bildschirm ermittelt werden, wird das
|
||||||
|
* {@link GraphicsEnvironment#getDefaultScreenDevice() Standardgerät}
|
||||||
|
* zurückgegeben.
|
||||||
|
*
|
||||||
|
* @return Das Anzeigegerät, auf dem ein neues Fenster angezeigt werden
|
||||||
|
* sollte.
|
||||||
|
*/
|
||||||
public static final GraphicsDevice getGraphicsDevice() {
|
public static final GraphicsDevice getGraphicsDevice() {
|
||||||
// Wir suchen den Bildschirm, der derzeit den Mauszeiger enthält, um
|
// Wir suchen den Bildschirm, der derzeit den Mauszeiger enthält, um
|
||||||
// das Zeichenfenster dort zu zentrieren.
|
// das Zeichenfenster dort zu zentrieren.
|
||||||
@@ -54,14 +70,14 @@ public class Zeichenfenster extends JFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Das Anzeigefenster, auf dem die ZM gestartet wurde (muss nicht gleich dem
|
* Das Anzeigegerät, auf dem die Zeichenmaschine gestartet wurde (muss nicht
|
||||||
* Aktuellen sein, wenn das Fenster verschoben wurde).
|
* gleich dem Aktuellen sein, wenn das Fenster verschoben wurde).
|
||||||
*/
|
*/
|
||||||
private GraphicsDevice displayDevice;
|
private final GraphicsDevice displayDevice;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bevorzugte Abmessungen der Zeichenleinwand. Für das Zeichenfenster hat
|
* Bevorzugte Abmessungen der Zeichenleinwand. Für das Zeichenfenster hat es
|
||||||
* es Priorität die Leinwand auf dieser Größe zu halten. Gegebenenfalls unter
|
* Priorität die Leinwand auf dieser Größe zu halten. Gegebenenfalls unter
|
||||||
* Missachtung anderer Größenvorgaben. Allerdings kann das Fenster keine
|
* Missachtung anderer Größenvorgaben. Allerdings kann das Fenster keine
|
||||||
* Garantie für die Größe der Leinwand übernehmen.
|
* Garantie für die Größe der Leinwand übernehmen.
|
||||||
*/
|
*/
|
||||||
@@ -78,40 +94,76 @@ public class Zeichenfenster extends JFrame {
|
|||||||
* verlassen. Wird von {@link #setFullscreen(boolean)} automatisch
|
* verlassen. Wird von {@link #setFullscreen(boolean)} automatisch
|
||||||
* hinzugefügt und entfernt.
|
* hinzugefügt und entfernt.
|
||||||
*/
|
*/
|
||||||
private KeyListener fullscreenExitListener = new KeyAdapter() {
|
private final KeyListener fullscreenExitListener = new KeyAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void keyPressed( KeyEvent e ) {
|
public void keyPressed( KeyEvent e ) {
|
||||||
if( e.getKeyCode() == KeyEvent.VK_ESCAPE ) {
|
if( e.getKeyCode() == KeyEvent.VK_ESCAPE ) {
|
||||||
// canvas.removeKeyListener(this);
|
// canvas.removeKeyListener(this);
|
||||||
setFullscreen(false);
|
setFullscreen(false);
|
||||||
|
e.consume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private Zeichenleinwand canvas;
|
// Die Zeichenleinwand dieses Fensters.
|
||||||
|
private final Zeichenleinwand canvas;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt ein neues Zeichenfenster mit dem angegebenen Titel und einer
|
||||||
|
* {@link Zeichenleinwand} in der angegebenen Größe.
|
||||||
|
*
|
||||||
|
* @param width Die Breite der Zeichenleinwand.
|
||||||
|
* @param height Die Höhe der Zeichenleinwand.
|
||||||
|
* @param title Der Titel des Fensters.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public Zeichenfenster( int width, int height, String title ) {
|
public Zeichenfenster( int width, int height, String title ) {
|
||||||
this(new Zeichenleinwand(width, height), title, getGraphicsDevice());
|
this(new Zeichenleinwand(width, height), title, getGraphicsDevice());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt ein neues Zeichenfenster mit dem angegebenen Titel und einer
|
||||||
|
* {@link Zeichenleinwand} in der angegebenen Größe auf dem angegebenen
|
||||||
|
* Anzeigegerät.
|
||||||
|
*
|
||||||
|
* @param width Die Breite der Zeichenleinwand.
|
||||||
|
* @param height Die Höhe der Zeichenleinwand.
|
||||||
|
* @param title Der Titel des Fensters.
|
||||||
|
* @param displayDevice Das Anzeigegerät für das Fenster.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public Zeichenfenster( int width, int height, String title, GraphicsDevice displayDevice ) {
|
public Zeichenfenster( int width, int height, String title, GraphicsDevice displayDevice ) {
|
||||||
this(new Zeichenleinwand(width, height), title, displayDevice);
|
this(new Zeichenleinwand(width, height), title, displayDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt ein neues Zeichenfenster mit dem angegebenen Titel und der
|
||||||
|
* angegebene {@link Zeichenleinwand}.
|
||||||
|
*
|
||||||
|
* @param canvas Die Zeichenleinwand.
|
||||||
|
* @param title Der Titel des Fensters.
|
||||||
|
*/
|
||||||
public Zeichenfenster( Zeichenleinwand canvas, String title ) {
|
public Zeichenfenster( Zeichenleinwand canvas, String title ) {
|
||||||
this(canvas, title, getGraphicsDevice());
|
this(canvas, title, getGraphicsDevice());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt ein neues Zeichenfenster mit dem angegebenen Titel und der
|
||||||
|
* angegebene {@link Zeichenleinwand} auf dem angegebenen Anzeigegerät.
|
||||||
|
*
|
||||||
|
* @param canvas Die Zeichenleinwand.
|
||||||
|
* @param title Der Titel des Fensters.
|
||||||
|
* @param displayDevice Das Anzeigegerät für das Fenster.
|
||||||
|
*/
|
||||||
public Zeichenfenster( Zeichenleinwand canvas, String title, GraphicsDevice displayDevice ) {
|
public Zeichenfenster( Zeichenleinwand canvas, String title, GraphicsDevice displayDevice ) {
|
||||||
super(Validator.requireNotNull(displayDevice).getDefaultConfiguration());
|
super(Validator.requireNotNull(displayDevice, "displayDevice").getDefaultConfiguration());
|
||||||
this.displayDevice = displayDevice;
|
this.displayDevice = displayDevice;
|
||||||
|
|
||||||
Validator.requireNotNull(canvas, "Every Zeichenfenster needs a Zeichenleinwand, but got <null>.");
|
Validator.requireNotNull(canvas, "Every Zeichenfenster needs a Zeichenleinwand, but got <null>.");
|
||||||
|
|
||||||
this.canvasPreferredWidth = canvas.getWidth();
|
this.canvasPreferredWidth = canvas.getWidth();
|
||||||
this.canvasPreferredHeight = canvas.getHeight();
|
this.canvasPreferredHeight = canvas.getHeight();
|
||||||
//this.add(canvas, BorderLayout.CENTER);
|
this.add(canvas, BorderLayout.CENTER);
|
||||||
this.add(canvas);
|
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
|
|
||||||
// Konfiguration des Frames
|
// Konfiguration des Frames
|
||||||
@@ -123,14 +175,19 @@ public class Zeichenfenster extends JFrame {
|
|||||||
try {
|
try {
|
||||||
if( Zeichenmaschine.MACOS ) {
|
if( Zeichenmaschine.MACOS ) {
|
||||||
URL iconUrl = Zeichenmaschine.class.getResource("icon_512.png");
|
URL iconUrl = Zeichenmaschine.class.getResource("icon_512.png");
|
||||||
|
if( iconUrl != null ) {
|
||||||
Image icon = ImageIO.read(iconUrl);
|
Image icon = ImageIO.read(iconUrl);
|
||||||
// Dock Icon in macOS setzen
|
// Dock Icon in macOS setzen
|
||||||
Taskbar taskbar = Taskbar.getTaskbar();
|
Taskbar taskbar = Taskbar.getTaskbar();
|
||||||
taskbar.setIconImage(icon);
|
taskbar.setIconImage(icon);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ArrayList<Image> icons = new ArrayList<>(4);
|
ArrayList<Image> icons = new ArrayList<>(4);
|
||||||
for( int size = 32; size <= 512; size *= size ) {
|
for( int size : new int[]{32, 64, 128, 512} ) {
|
||||||
icons.add(ImageIO.read(new File("icon_" + size + ".png")));
|
URL icnUrl = Zeichenmaschine.class.getResource("icon_" + size + ".png");
|
||||||
|
if( icnUrl != null ) {
|
||||||
|
icons.add(ImageIO.read(icnUrl));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setIconImages(icons);
|
this.setIconImages(icons);
|
||||||
@@ -144,25 +201,54 @@ public class Zeichenfenster extends JFrame {
|
|||||||
// Fenster zusammenbauen, auf dem Bildschirm positionieren ...
|
// Fenster zusammenbauen, auf dem Bildschirm positionieren ...
|
||||||
this.pack();
|
this.pack();
|
||||||
this.setResizable(false);
|
this.setResizable(false);
|
||||||
|
this.setFocusable(true);
|
||||||
this.setLocationByPlatform(true);
|
this.setLocationByPlatform(true);
|
||||||
// this.centerFrame();
|
// this.centerFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert das Anzeigegerät, auf dem dieses Fenster erstellt wurde.
|
||||||
|
* <p>
|
||||||
|
* Das Anzeigegerät muss nicht unbedingt gleich dem sein, auf dem sich das
|
||||||
|
* Fenster derzeit befindet, wenn das Fenster verschoben wurde.
|
||||||
|
*
|
||||||
|
* @return Das Anzeigegerät.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public GraphicsDevice getDisplayDevice() {
|
public GraphicsDevice getDisplayDevice() {
|
||||||
return displayDevice;
|
return displayDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert die Abmessungen des Anzeigegeräts, auf dem das Fenster gestartet
|
||||||
|
* wurde.
|
||||||
|
*
|
||||||
|
* @return Die Abmessungen des Anzeigegeräts.
|
||||||
|
*/
|
||||||
public Rectangle getScreenBounds() {
|
public Rectangle getScreenBounds() {
|
||||||
|
// return GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
|
||||||
return displayDevice.getDefaultConfiguration().getBounds();
|
return displayDevice.getDefaultConfiguration().getBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert die Zeichenleinwand dieses Fensters.
|
||||||
|
*
|
||||||
|
* @return Die Zeichenleinwand.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public Zeichenleinwand getCanvas() {
|
public Zeichenleinwand getCanvas() {
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert die Abmessungen der Zeichenleinwand zurück.
|
||||||
|
*
|
||||||
|
* @return Die Abmessungen der Zeichenleinwand.
|
||||||
|
*/
|
||||||
public Rectangle getCanvasBounds() {
|
public Rectangle getCanvasBounds() {
|
||||||
return canvas.getBounds();
|
return canvas.getBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zentriert das Zeichenfenster auf dem aktuellen Bildschirm.
|
* Zentriert das Zeichenfenster auf dem aktuellen Bildschirm.
|
||||||
*/
|
*/
|
||||||
@@ -175,8 +261,14 @@ public class Zeichenfenster extends JFrame {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Größe der Zeichenleinwand auf die angegebenen Werte.
|
||||||
|
*
|
||||||
|
* @param newWidth Neue Breite der Zeichenleinwand.
|
||||||
|
* @param newHeight Neue Höhe der Zeichenleinwand.
|
||||||
|
*/
|
||||||
public void setCanvasSize( int newWidth, int newHeight ) {
|
public void setCanvasSize( int newWidth, int newHeight ) {
|
||||||
// TODO: (ngb) Put constains on max/min frame/canvas size
|
// TODO: (ngb) Put constrains on max/min frame/canvas size
|
||||||
if( fullscreen ) {
|
if( fullscreen ) {
|
||||||
canvasPreferredWidth = newWidth;
|
canvasPreferredWidth = newWidth;
|
||||||
canvasPreferredHeight = newHeight;
|
canvasPreferredHeight = newHeight;
|
||||||
@@ -205,30 +297,62 @@ public class Zeichenfenster extends JFrame {
|
|||||||
public final void setFullscreen( boolean pEnable ) {
|
public final void setFullscreen( boolean pEnable ) {
|
||||||
// See https://docs.oracle.com/javase/tutorial/extra/fullscreen/index.html
|
// See https://docs.oracle.com/javase/tutorial/extra/fullscreen/index.html
|
||||||
if( displayDevice.isFullScreenSupported() ) {
|
if( displayDevice.isFullScreenSupported() ) {
|
||||||
|
// Temporarily stop rendering
|
||||||
|
while( canvas.isRendering() ) {
|
||||||
|
try {
|
||||||
|
canvas.suspendRendering();
|
||||||
|
} catch( InterruptedException ex ) {
|
||||||
|
LOG.info(ex, "setFullsceen(true) was interrupted and canceled.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if( pEnable && !fullscreen ) {
|
if( pEnable && !fullscreen ) {
|
||||||
// frame.setUndecorated(true);
|
// Activate fullscreen
|
||||||
this.setResizable(false); // Should be set anyway
|
dispose();
|
||||||
|
setUndecorated(true);
|
||||||
|
setResizable(false);
|
||||||
displayDevice.setFullScreenWindow(this);
|
displayDevice.setFullScreenWindow(this);
|
||||||
|
|
||||||
java.awt.Rectangle bounds = getScreenBounds();
|
|
||||||
// TODO: (ngb) We need to switch layouts to allow the LayoutManger to maximize the canvas
|
|
||||||
canvas.setSize(bounds.width, bounds.height);
|
|
||||||
// Register ESC to exit fullscreen
|
// Register ESC to exit fullscreen
|
||||||
canvas.addKeyListener(fullscreenExitListener);
|
canvas.addKeyListener(fullscreenExitListener);
|
||||||
|
|
||||||
|
// Reset canvas size to its new bounds to recreate buffer and drawing surface
|
||||||
|
java.awt.Rectangle canvasBounds = getCanvasBounds();
|
||||||
|
canvas.setSize(canvasBounds.width, canvasBounds.height);
|
||||||
|
//canvas.requestFocus();
|
||||||
|
canvas.requestFocus();
|
||||||
|
|
||||||
fullscreen = true;
|
fullscreen = true;
|
||||||
} else if( !pEnable && fullscreen ) {
|
} else if( !pEnable && fullscreen ) {
|
||||||
fullscreen = false;
|
displayDevice.setFullScreenWindow(null);
|
||||||
|
dispose();
|
||||||
|
setUndecorated(false);
|
||||||
|
setResizable(false);
|
||||||
|
|
||||||
canvas.removeKeyListener(fullscreenExitListener);
|
canvas.removeKeyListener(fullscreenExitListener);
|
||||||
displayDevice.setFullScreenWindow(null);
|
|
||||||
canvas.setSize(canvasPreferredWidth, canvasPreferredHeight);
|
canvas.setSize(canvasPreferredWidth, canvasPreferredHeight);
|
||||||
this.pack();
|
|
||||||
// frame.setUndecorated(false);
|
setVisible(true);
|
||||||
|
pack();
|
||||||
|
|
||||||
|
//canvas.requestFocus();
|
||||||
|
canvas.requestFocus();
|
||||||
|
|
||||||
|
fullscreen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resume rendering
|
||||||
|
canvas.resumeRendering();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft, ob sich dieses Zeichenfenster im Vollbild befindet.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn das Fenster im Vollbild ist, {@code false}
|
||||||
|
* sonst.
|
||||||
|
*/
|
||||||
public boolean isFullscreen() {
|
public boolean isFullscreen() {
|
||||||
Window win = displayDevice.getFullScreenWindow();
|
Window win = displayDevice.getFullScreenWindow();
|
||||||
return fullscreen && win.equals(this);
|
return fullscreen && win.equals(this);
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package schule.ngb.zm;
|
package schule.ngb.zm;
|
||||||
|
|
||||||
import schule.ngb.zm.layers.ColorLayer;
|
|
||||||
import schule.ngb.zm.layers.ShapesLayer;
|
|
||||||
import schule.ngb.zm.util.Log;
|
import schule.ngb.zm.util.Log;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.Canvas;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Toolkit;
|
||||||
import java.awt.image.BufferStrategy;
|
import java.awt.image.BufferStrategy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Eine Leinwand ist die Hauptkomponente einer Zeichenmaschine. Sie besteht aus
|
* Eine Leinwand ist die Hauptkomponente einer Zeichenmaschine. Sie besteht aus
|
||||||
@@ -24,10 +24,14 @@ import java.util.concurrent.LinkedBlockingQueue;
|
|||||||
*/
|
*/
|
||||||
public class Zeichenleinwand extends Canvas {
|
public class Zeichenleinwand extends Canvas {
|
||||||
|
|
||||||
/**
|
// Lokales Lock für Rendervorgänge.
|
||||||
* Liste der hinzugefügten Ebenen.
|
private final Object[] renderLock = new Object[0];
|
||||||
*/
|
|
||||||
private LinkedList<Layer> layers;
|
// Liste der hinzugefügten Ebenen.
|
||||||
|
private final List<Layer> layers;
|
||||||
|
|
||||||
|
// Status der Zeichenleinwand.
|
||||||
|
private boolean rendering = false, suspended = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt eine neue Zeichenleinwand mit einer festen Größe.
|
* Erstellt eine neue Zeichenleinwand mit einer festen Größe.
|
||||||
@@ -41,11 +45,49 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
this.setMinimumSize(getSize());
|
this.setMinimumSize(getSize());
|
||||||
this.setBackground(Constants.DEFAULT_BACKGROUND.getJavaColor());
|
this.setBackground(Constants.DEFAULT_BACKGROUND.getJavaColor());
|
||||||
|
|
||||||
// Liste der Ebenen initialisieren und die Standardebenen einfügen
|
layers = Collections.synchronizedList(new LinkedList<>());
|
||||||
layers = new LinkedList<>();
|
|
||||||
synchronized( layers ) {
|
|
||||||
layers.add(new ColorLayer(width, height, Constants.DEFAULT_BACKGROUND));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob die Leinwand ihren Inhalt gerade zeichnet.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn die Inhalte gerade gezeichnet werden.
|
||||||
|
*/
|
||||||
|
public boolean isRendering() {
|
||||||
|
return rendering;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pausiert das Zeichnen der Leinwand kurzzeitig.
|
||||||
|
* <p>
|
||||||
|
* Falls die Leinwand gerade beim Zeichnen ist
|
||||||
|
* ({@code isRendering() == true}, blockt die Methode den aufrufenden Thread
|
||||||
|
* so lange, bis das Rendern beendet ist. Danach wird die Ebene nicht mehr
|
||||||
|
* neu gezeichnet, bis {@link #resumeRendering()} aufgerufen wird.
|
||||||
|
* <p>
|
||||||
|
* Das Zeichnen sollte nur dann unterbrochen werden, wenn sich der Kontext
|
||||||
|
* der Canvas-Komponente in seinem Elterncontainer ändert, um Fehler bei
|
||||||
|
* einer fehlenden Container-Hierarchie zu vermeiden.
|
||||||
|
*
|
||||||
|
* @throws InterruptedException Falls der Thread beim Warten unterbrochen
|
||||||
|
* wird.
|
||||||
|
*/
|
||||||
|
public void suspendRendering() throws InterruptedException {
|
||||||
|
synchronized( renderLock ) {
|
||||||
|
if( isRendering() ) {
|
||||||
|
renderLock.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
suspended = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt das Zeichnen der Leinwand fort, falls es zuvor mit
|
||||||
|
* {@link #suspendRendering()} ausgesetzt wurde.
|
||||||
|
*/
|
||||||
|
public void resumeRendering() {
|
||||||
|
suspended = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,26 +120,23 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
*/
|
*/
|
||||||
public void addLayer( Layer layer ) {
|
public void addLayer( Layer layer ) {
|
||||||
if( layer != null ) {
|
if( layer != null ) {
|
||||||
synchronized( layers ) {
|
|
||||||
layer.setSize(getWidth(), getHeight());
|
layer.setSize(getWidth(), getHeight());
|
||||||
layers.add(layer);
|
layers.add(layer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fügt der Zeichenleinwand eine Ebene an einem bestimmten Index hinzu. Wenn
|
* Fügt der Zeichenleinwand eine Ebene an einem bestimmten Index hinzu. Wenn
|
||||||
* der Index noch nicht existiert (also größer als die
|
* der Index noch nicht existiert (also größer als die
|
||||||
* {@link #getLayerCount() Anzahl der Ebenen} ist, dann wird die neue Ebene
|
* {@link #getLayerCount() Anzahl der Ebenen} ist), dann wird die neue Ebene
|
||||||
* als letzte eingefügt. Die aufrufende Methode kann also nicht sicher sein,
|
* als letzte eingefügt. Die aufrufende Methode kann also nicht sicher sein,
|
||||||
* dass die neue Ebene am Ende wirklich am Index {@code i} steht.
|
* 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.
|
* @param layer Die neue Ebene.
|
||||||
*/
|
*/
|
||||||
public void addLayer( int i, Layer layer ) {
|
public void addLayer( int i, Layer layer ) {
|
||||||
if( layer != null ) {
|
if( layer != null ) {
|
||||||
synchronized( layers ) {
|
|
||||||
layer.setSize(getWidth(), getHeight());
|
layer.setSize(getWidth(), getHeight());
|
||||||
if( i > layers.size() ) {
|
if( i > layers.size() ) {
|
||||||
layers.add(layer);
|
layers.add(layer);
|
||||||
@@ -106,7 +145,6 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die Anzahl der {@link Layer Ebenen} in dieser Leinwand zurück.
|
* Gibt die Anzahl der {@link Layer Ebenen} in dieser Leinwand zurück.
|
||||||
@@ -118,19 +156,19 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die Liste der bisher hinzugefügten Ebenen zurück.
|
* Gibt eine Kopie der Liste der bisher hinzugefügten Ebenen zurück.
|
||||||
*
|
*
|
||||||
* @return Liste der Ebenen.
|
* @return Liste der Ebenen.
|
||||||
*/
|
*/
|
||||||
public java.util.List<Layer> getLayers() {
|
public List<Layer> getLayers() {
|
||||||
return layers;
|
return List.copyOf(layers);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holt die Ebene am Index <var>i</var> (beginnend bei 0).
|
* Holt die Ebene am Index {@code i} (beginnend bei 0).
|
||||||
*
|
*
|
||||||
* @param i Index der Ebene (beginnend bei 0).
|
* @param i Index der Ebene (beginnend bei 0).
|
||||||
* @return Die Ebene am Index <var>i</var> oder {@code null}.
|
* @return Die Ebene am Index {@code i} oder {@code null}.
|
||||||
* @throws IndexOutOfBoundsException Falls der Index nicht existiert.
|
* @throws IndexOutOfBoundsException Falls der Index nicht existiert.
|
||||||
*/
|
*/
|
||||||
public Layer getLayer( int i ) {
|
public Layer getLayer( int i ) {
|
||||||
@@ -145,14 +183,16 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
* Sucht die erste Ebene des angegebenen Typs aus der Liste der Ebenen.
|
* Sucht die erste Ebene des angegebenen Typs aus der Liste der Ebenen.
|
||||||
* Existiert keine solche Ebene, wird {@code null} zurückgegeben.
|
* Existiert keine solche Ebene, wird {@code null} zurückgegeben.
|
||||||
*
|
*
|
||||||
* @param clazz Typ der Ebene.
|
* @param type Klasse der Ebenen, die abgefragt werden.
|
||||||
* @param <L>
|
* @param <L> Typ der Ebenen, die abgefragt werden.
|
||||||
* @return Erste Ebene vom angegeben Typ.
|
* @return Erste Ebene vom angegeben Typ.
|
||||||
*/
|
*/
|
||||||
public <L extends Layer> L getLayer( Class<L> clazz ) {
|
public <L extends Layer> L getLayer( Class<L> type ) {
|
||||||
|
synchronized( layers ) {
|
||||||
for( Layer layer : layers ) {
|
for( Layer layer : layers ) {
|
||||||
if( layer.getClass().equals(clazz) ) {
|
if( layer.getClass().equals(type) ) {
|
||||||
return clazz.cast(layer);
|
return type.cast(layer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -163,44 +203,67 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
* gibt diese als Liste zurück. Die Reihenfolge in der Liste entspricht der
|
* gibt diese als Liste zurück. Die Reihenfolge in der Liste entspricht der
|
||||||
* Reihenfolge der Ebenen in der Leinwand (von unten nach oben).
|
* Reihenfolge der Ebenen in der Leinwand (von unten nach oben).
|
||||||
*
|
*
|
||||||
* @param pClazz
|
* @param type Klasse der Ebenen, die abgefragt werden.
|
||||||
* @param <L>
|
* @param <L> Typ der Ebenen, die abgefragt werden.
|
||||||
* @return
|
* @return Eine Liste mit den vorhandenen Ebenen des abgefragten Typs.
|
||||||
*/
|
*/
|
||||||
public <L extends Layer> java.util.List<L> getLayers( Class<L> pClazz ) {
|
@SuppressWarnings( "unused" )
|
||||||
|
public <L extends Layer> List<L> getLayers( Class<L> type ) {
|
||||||
ArrayList<L> result = new ArrayList<>(layers.size());
|
ArrayList<L> result = new ArrayList<>(layers.size());
|
||||||
|
synchronized( layers ) {
|
||||||
for( Layer layer : layers ) {
|
for( Layer layer : layers ) {
|
||||||
if( layer.getClass().equals(pClazz) ) {
|
if( layer.getClass().equals(type) ) {
|
||||||
result.add(pClazz.cast(layer));
|
result.add(type.cast(layer));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt die angegebene Ebene von dieser Zeichenleinwand.
|
||||||
|
*
|
||||||
|
* @param pLayer Die Ebene, die entfernt werden soll.
|
||||||
|
* @return {@code true}, wenn die Liste vorhanden war und entfernt wurde,
|
||||||
|
* {@code false} sonst.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public boolean removeLayer( Layer pLayer ) {
|
public boolean removeLayer( Layer pLayer ) {
|
||||||
synchronized( layers ) {
|
|
||||||
return layers.remove(pLayer);
|
return layers.remove(pLayer);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void removeLayers( Layer... pLayers ) {
|
/**
|
||||||
|
* Entfernt alle angegebenen Ebenen von dieser Zeichenleinwand.
|
||||||
|
*
|
||||||
|
* @param removeLayers Die Ebenen, die entfernt werden sollen.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
|
public void removeLayers( Layer... removeLayers ) {
|
||||||
synchronized( layers ) {
|
synchronized( layers ) {
|
||||||
for( Layer layer : pLayers ) {
|
for( Layer layer : removeLayers ) {
|
||||||
layers.remove(layer);
|
layers.remove(layer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt alle vorhandenen Ebenen von dieser Zeichenleinwand.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public void clearLayers() {
|
public void clearLayers() {
|
||||||
synchronized( layers ) {
|
|
||||||
layers.clear();
|
layers.clear();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aktualisiert alle {@link Layer Ebenen}, die dieser Zeichenleinwand
|
||||||
|
* hinzugefügt wurden.
|
||||||
|
*
|
||||||
|
* @param delta Die Zeit seit dem letzten Aufruf in Sekunden.
|
||||||
|
* @see Layer#update(double)
|
||||||
|
*/
|
||||||
public void updateLayers( double delta ) {
|
public void updateLayers( double delta ) {
|
||||||
synchronized( layers ) {
|
synchronized( layers ) {
|
||||||
List<Layer> it = List.copyOf(layers);
|
for( Layer layer : List.copyOf(layers) ) {
|
||||||
for( Layer layer : it ) {
|
|
||||||
layer.update(delta);
|
layer.update(delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,15 +293,14 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zeichnet den Inhalt aller {@link Layer Ebenen} in den Grafik-Kontext.
|
* Zeichnet den Inhalt aller {@link Layer Ebenen} in den Grafikkontext.
|
||||||
*
|
*
|
||||||
* @param graphics
|
* @param graphics Der Grafikkontext.
|
||||||
*/
|
*/
|
||||||
public void draw( Graphics graphics ) {
|
public void draw( Graphics graphics ) {
|
||||||
Graphics2D g2d = (Graphics2D) graphics.create();
|
Graphics2D g2d = (Graphics2D) graphics.create();
|
||||||
synchronized( layers ) {
|
synchronized( layers ) {
|
||||||
List<Layer> it = List.copyOf(layers);
|
for( Layer layer : layers ) {
|
||||||
for( Layer layer : it ) {
|
|
||||||
layer.draw(g2d);
|
layer.draw(g2d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,11 +311,13 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
* Zeigt den aktuellen Inhalt der Zeichenleinwand an.
|
* Zeigt den aktuellen Inhalt der Zeichenleinwand an.
|
||||||
*/
|
*/
|
||||||
public void render() {
|
public void render() {
|
||||||
|
if( !suspended && isDisplayable() ) {
|
||||||
if( getBufferStrategy() == null ) {
|
if( getBufferStrategy() == null ) {
|
||||||
allocateBuffer();
|
allocateBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
if( isDisplayable() ) {
|
synchronized( renderLock ) {
|
||||||
|
rendering = true;
|
||||||
BufferStrategy strategy = this.getBufferStrategy();
|
BufferStrategy strategy = this.getBufferStrategy();
|
||||||
if( strategy != null ) {
|
if( strategy != null ) {
|
||||||
do {
|
do {
|
||||||
@@ -262,8 +326,7 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
g2d.clearRect(0, 0, getWidth(), getHeight());
|
g2d.clearRect(0, 0, getWidth(), getHeight());
|
||||||
|
|
||||||
synchronized( layers ) {
|
synchronized( layers ) {
|
||||||
List<Layer> it = List.copyOf(layers);
|
for( Layer layer : List.copyOf(layers) ) {
|
||||||
for( Layer layer : it ) {
|
|
||||||
layer.draw(g2d);
|
layer.draw(g2d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,6 +344,9 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
// Repeat the rendering if the drawing buffer was lost
|
// Repeat the rendering if the drawing buffer was lost
|
||||||
} while( strategy.contentsLost() );
|
} while( strategy.contentsLost() );
|
||||||
}
|
}
|
||||||
|
rendering = false;
|
||||||
|
renderLock.notifyAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import java.awt.image.BufferedImage;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hauptklasse der Zeichenmaschine.
|
* Hauptklasse der Zeichenmaschine.
|
||||||
@@ -37,43 +38,6 @@ public class Zeichenmaschine extends Constants {
|
|||||||
IN_BLUEJ = System.getProperty("java.class.path").contains("bluej");
|
IN_BLUEJ = System.getProperty("java.class.path").contains("bluej");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt an, ob die Zeichenmaschine unter macOS gestartet wurde.
|
|
||||||
*/
|
|
||||||
public static final boolean MACOS;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt an, ob die Zeichenmaschine unter Windows gestartet wurde.
|
|
||||||
*/
|
|
||||||
public static final boolean WINDOWS;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt an, ob die Zeichenmaschine unter Linux gestartet wurde.
|
|
||||||
*/
|
|
||||||
public static final boolean LINUX;
|
|
||||||
|
|
||||||
static {
|
|
||||||
final String name = System.getProperty("os.name");
|
|
||||||
|
|
||||||
if( name.contains("Mac") ) {
|
|
||||||
MACOS = true;
|
|
||||||
WINDOWS = false;
|
|
||||||
LINUX = false;
|
|
||||||
} else if( name.contains("Windows") ) {
|
|
||||||
MACOS = false;
|
|
||||||
WINDOWS = true;
|
|
||||||
LINUX = false;
|
|
||||||
} else if( name.equals("Linux") ) { // true for the ibm vm
|
|
||||||
MACOS = false;
|
|
||||||
WINDOWS = false;
|
|
||||||
LINUX = true;
|
|
||||||
} else {
|
|
||||||
MACOS = false;
|
|
||||||
WINDOWS = false;
|
|
||||||
LINUX = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Objektvariablen, die von Unterklassen benutzt werden können.
|
* Objektvariablen, die von Unterklassen benutzt werden können.
|
||||||
*/
|
*/
|
||||||
@@ -111,14 +75,14 @@ public class Zeichenmaschine extends Constants {
|
|||||||
/**
|
/**
|
||||||
* Das Zeichenfenster der Zeichenmaschine
|
* Das Zeichenfenster der Zeichenmaschine
|
||||||
*/
|
*/
|
||||||
private Zeichenfenster frame;
|
protected Zeichenfenster frame;
|
||||||
|
|
||||||
// Aktueller Zustand der Zeichenmaschine.
|
// Aktueller Zustand der Zeichenmaschine.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zustand der Zeichenmaschine insgesamt
|
* Zustand der Zeichenmaschine insgesamt
|
||||||
*/
|
*/
|
||||||
private Options.AppState state = Options.AppState.INITIALIZING;
|
private Options.AppState state;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zustand des update/draw Threads
|
* Zustand des update/draw Threads
|
||||||
@@ -128,7 +92,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
/**
|
/**
|
||||||
* Ob der Zeichenthread noch laufen soll, oder beendet.
|
* Ob der Zeichenthread noch laufen soll, oder beendet.
|
||||||
*/
|
*/
|
||||||
private boolean running = false;
|
private boolean running;
|
||||||
|
|
||||||
private boolean terminateImediately = false;
|
private boolean terminateImediately = false;
|
||||||
|
|
||||||
@@ -141,7 +105,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* Ob die ZM bei nicht überschriebener update() Methode stoppen soll,
|
* Ob die ZM bei nicht überschriebener update() Methode stoppen soll,
|
||||||
* oder trotzdem weiterläuft.
|
* oder trotzdem weiterläuft.
|
||||||
*/
|
*/
|
||||||
private boolean run_once = true;
|
private final boolean run_once;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aktuelle Frames pro Sekunde der Zeichenmaschine.
|
* Aktuelle Frames pro Sekunde der Zeichenmaschine.
|
||||||
@@ -151,17 +115,17 @@ public class Zeichenmaschine extends Constants {
|
|||||||
/**
|
/**
|
||||||
* Hauptthread der Zeichenmaschine.
|
* Hauptthread der Zeichenmaschine.
|
||||||
*/
|
*/
|
||||||
private Thread mainThread;
|
private final Thread mainThread;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queue für geplante Aufgaben
|
* Queue für geplante Aufgaben
|
||||||
*/
|
*/
|
||||||
private DelayQueue<DelayedTask> taskQueue = new DelayQueue<>();
|
private final DelayQueue<DelayedTask> taskQueue = new DelayQueue<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queue für abgefangene InputEvents
|
* Queue für abgefangene InputEvents
|
||||||
*/
|
*/
|
||||||
private BlockingQueue<InputEvent> eventQueue = new LinkedBlockingQueue<>();
|
private final BlockingQueue<InputEvent> eventQueue = new LinkedBlockingQueue<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt an, ob nach Ende des Hauptthreads das Programm beendet werden soll,
|
* Gibt an, ob nach Ende des Hauptthreads das Programm beendet werden soll,
|
||||||
@@ -274,9 +238,15 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* @param run_once {@code true} beendet die Zeichenmaschine nach einem
|
* @param run_once {@code true} beendet die Zeichenmaschine nach einem
|
||||||
* Aufruf von {@code draw()}.
|
* Aufruf von {@code draw()}.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("static-access")
|
||||||
public Zeichenmaschine( int width, int height, String title, boolean run_once ) {
|
public Zeichenmaschine( int width, int height, String title, boolean run_once ) {
|
||||||
LOG.info("Starting " + APP_NAME + " " + APP_VERSION);
|
LOG.info("Starting " + APP_NAME + " " + APP_VERSION);
|
||||||
|
|
||||||
|
// Register Cmd+Q on macOS
|
||||||
|
if( Constants.MACOS ) {
|
||||||
|
System.setProperty("apple.eawt.quitStrategy", "CLOSE_ALL_WINDOWS");
|
||||||
|
}
|
||||||
|
|
||||||
// Erstellen der Leinwand
|
// Erstellen der Leinwand
|
||||||
canvas = new Zeichenleinwand(width, height);
|
canvas = new Zeichenleinwand(width, height);
|
||||||
|
|
||||||
@@ -312,8 +282,15 @@ public class Zeichenmaschine extends Constants {
|
|||||||
canvas.addMouseWheelListener(inputListener);
|
canvas.addMouseWheelListener(inputListener);
|
||||||
canvas.addKeyListener(inputListener);
|
canvas.addKeyListener(inputListener);
|
||||||
|
|
||||||
|
/*KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
|
||||||
|
@Override
|
||||||
|
public boolean dispatchKeyEvent( KeyEvent e ) {
|
||||||
|
enqueueEvent(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});*/
|
||||||
|
|
||||||
// Programm beenden, wenn Fenster geschlossen wird
|
// Programm beenden, wenn Fenster geschlossen wird
|
||||||
// TODO: (ngb) Der Listener hat zu viel FUnktionalität -> nach quit() / exit() auslagern
|
|
||||||
frame.addWindowListener(new WindowAdapter() {
|
frame.addWindowListener(new WindowAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void windowClosing( WindowEvent e ) {
|
public void windowClosing( WindowEvent e ) {
|
||||||
@@ -350,17 +327,15 @@ public class Zeichenmaschine extends Constants {
|
|||||||
*
|
*
|
||||||
* @param title
|
* @param title
|
||||||
*/
|
*/
|
||||||
private final Zeichenfenster createFrame( Zeichenleinwand c, String title ) {
|
private Zeichenfenster createFrame( Zeichenleinwand c, String title ) {
|
||||||
while( frame == null ) {
|
while( frame == null ) {
|
||||||
try {
|
try {
|
||||||
TaskRunner.invokeLater(new Runnable() {
|
TaskRunner.invokeLater(() -> {
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Zeichenfenster.setLookAndFeel();
|
Zeichenfenster.setLookAndFeel();
|
||||||
frame = new Zeichenfenster(canvas, title);
|
frame = new Zeichenfenster(canvas, title);
|
||||||
}
|
|
||||||
}).get();
|
}).get();
|
||||||
} catch( InterruptedException e ) {
|
} catch( InterruptedException e ) {
|
||||||
|
// Keep waiting
|
||||||
} catch( ExecutionException e ) {
|
} catch( ExecutionException e ) {
|
||||||
LOG.error(e, "Error initializing application frame: %s", e.getMessage());
|
LOG.error(e, "Error initializing application frame: %s", e.getMessage());
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
@@ -392,10 +367,18 @@ public class Zeichenmaschine extends Constants {
|
|||||||
public final void setFullscreen( boolean pEnable ) {
|
public final void setFullscreen( boolean pEnable ) {
|
||||||
if( pEnable && !frame.isFullscreen() ) {
|
if( pEnable && !frame.isFullscreen() ) {
|
||||||
frame.setFullscreen(true);
|
frame.setFullscreen(true);
|
||||||
|
|
||||||
|
canvasWidth = canvas.getWidth();
|
||||||
|
canvasHeight = canvas.getHeight();
|
||||||
|
|
||||||
if( frame.isFullscreen() )
|
if( frame.isFullscreen() )
|
||||||
fullscreenChanged();
|
fullscreenChanged();
|
||||||
} else if( !pEnable && frame.isFullscreen() ) {
|
} else if( !pEnable && frame.isFullscreen() ) {
|
||||||
frame.setFullscreen(false);
|
frame.setFullscreen(false);
|
||||||
|
|
||||||
|
canvasWidth = canvas.getWidth();
|
||||||
|
canvasHeight = canvas.getHeight();
|
||||||
|
|
||||||
if( !frame.isFullscreen() )
|
if( !frame.isFullscreen() )
|
||||||
fullscreenChanged();
|
fullscreenChanged();
|
||||||
}
|
}
|
||||||
@@ -444,9 +427,18 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* im Zeichenfenster an.
|
* im Zeichenfenster an.
|
||||||
*/
|
*/
|
||||||
public final void redraw() {
|
public final void redraw() {
|
||||||
if( state == Options.AppState.PAUSED ) {
|
if( state == Options.AppState.PAUSED
|
||||||
|
|| state == Options.AppState.TERMINATED ) {
|
||||||
draw();
|
draw();
|
||||||
}
|
}
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt den aktuellen Inhalt der {@link Zeichenleinwand}
|
||||||
|
* im Zeichenfenster an, ohne vorher {@link #draw()} aufzurufen.
|
||||||
|
*/
|
||||||
|
public final void render() {
|
||||||
canvas.render();
|
canvas.render();
|
||||||
// canvas.invalidate();
|
// canvas.invalidate();
|
||||||
// frame.repaint();
|
// frame.repaint();
|
||||||
@@ -546,6 +538,11 @@ public class Zeichenmaschine extends Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final void exitNow() {
|
public final void exitNow() {
|
||||||
|
// Do nothing, when already quitting
|
||||||
|
if( state == Options.AppState.QUITING ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if( running ) {
|
if( running ) {
|
||||||
running = false;
|
running = false;
|
||||||
terminateImediately = true;
|
terminateImediately = true;
|
||||||
@@ -584,6 +581,8 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* @see System#exit(int)
|
* @see System#exit(int)
|
||||||
*/
|
*/
|
||||||
public final void quit( boolean exit ) {
|
public final void quit( boolean exit ) {
|
||||||
|
state = Options.AppState.QUITING;
|
||||||
|
TaskRunner.invokeLater(() -> {
|
||||||
frame.setVisible(false);
|
frame.setVisible(false);
|
||||||
canvas.dispose();
|
canvas.dispose();
|
||||||
frame.dispose();
|
frame.dispose();
|
||||||
@@ -591,6 +590,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
if( exit ) {
|
if( exit ) {
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -601,11 +601,11 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* @see Zeichenleinwand#setSize(int, int)
|
* @see Zeichenleinwand#setSize(int, int)
|
||||||
*/
|
*/
|
||||||
public final void setSize( int width, int height ) {
|
public final void setSize( int width, int height ) {
|
||||||
frame.setSize(width, height);
|
frame.setCanvasSize(width, height);
|
||||||
|
|
||||||
java.awt.Rectangle canvasBounds = frame.getCanvasBounds();
|
java.awt.Rectangle canvasBounds = frame.getCanvasBounds();
|
||||||
canvasWidth = (int) canvasBounds.getWidth();
|
canvasWidth = canvasBounds.width;
|
||||||
canvasHeight = (int) canvasBounds.getHeight();
|
canvasHeight = canvasBounds.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -679,9 +679,9 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* Gibt die erste (unterste) {@link Layer Ebene} der angegebenen Klasse
|
* Gibt die erste (unterste) {@link Layer Ebene} der angegebenen Klasse
|
||||||
* zurück.
|
* zurück.
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre><code>
|
||||||
* DrawingLayer draw = getLayer(DrawingLayer.class);
|
* DrawingLayer draw = getLayer(DrawingLayer.class);
|
||||||
* </pre>
|
* </code></pre>
|
||||||
*
|
*
|
||||||
* @param layerClass
|
* @param layerClass
|
||||||
* @param <LT>
|
* @param <LT>
|
||||||
@@ -947,9 +947,9 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* <p>
|
* <p>
|
||||||
* Die Konstanten der Klasse {@link Cursor} definieren 13 Standardzeiger,
|
* Die Konstanten der Klasse {@link Cursor} definieren 13 Standardzeiger,
|
||||||
* die durch angabe der Nummer geladen werden können.
|
* die durch angabe der Nummer geladen werden können.
|
||||||
* <pre>
|
* <pre><code>
|
||||||
* setCursor(Cursor.HAND_CURSOR);
|
* setCursor(Cursor.HAND_CURSOR);
|
||||||
* </pre>
|
* </code></pre>
|
||||||
*
|
*
|
||||||
* @param pPredefinedCursor Eine der Cursor-Konstanten.
|
* @param pPredefinedCursor Eine der Cursor-Konstanten.
|
||||||
* @see Cursor
|
* @see Cursor
|
||||||
@@ -1022,9 +1022,9 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* {@code delta} wird in Sekunden angegeben. Um eine Form zum Beispiel um
|
* {@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
|
* {@code 50} Pixel pro Sekunde in {@code x}-Richtung zu bewegen, kann so
|
||||||
* vorgegangen werden:
|
* vorgegangen werden:
|
||||||
* <pre>
|
* <pre><code>
|
||||||
* shape.move(50*delta, 0.0);
|
* shape.move(50*delta, 0.0);
|
||||||
* </pre>
|
* </code></pre>
|
||||||
*
|
*
|
||||||
* @param delta
|
* @param delta
|
||||||
*/
|
*/
|
||||||
@@ -1100,6 +1100,9 @@ public class Zeichenmaschine extends Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if( isPaused() || isTerminated() ) {
|
if( isPaused() || isTerminated() ) {
|
||||||
|
if( MouseEvent.class.isInstance(evt) ) {
|
||||||
|
saveMousePosition((MouseEvent)evt);
|
||||||
|
}
|
||||||
dispatchEvents();
|
dispatchEvents();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1327,7 +1330,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
long overslept = 0L;
|
long overslept = 0L;
|
||||||
// Interne Zähler für tick und runtime
|
// Interne Zähler für tick und runtime
|
||||||
int _tick = 0;
|
int _tick = 0;
|
||||||
long _runtime = 0;
|
long _runtime;
|
||||||
// Öffentliche Zähler für Unterklassen
|
// Öffentliche Zähler für Unterklassen
|
||||||
tick = 0;
|
tick = 0;
|
||||||
runtime = 0;
|
runtime = 0;
|
||||||
@@ -1390,7 +1393,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Display the current buffer content
|
// Display the current buffer content
|
||||||
if( canvas != null ) {
|
if( canvas != null && frame.isDisplayable() ) {
|
||||||
canvas.render();
|
canvas.render();
|
||||||
// canvas.invalidate();
|
// canvas.invalidate();
|
||||||
// frame.repaint();
|
// frame.repaint();
|
||||||
@@ -1444,7 +1447,11 @@ public class Zeichenmaschine extends Constants {
|
|||||||
Thread.yield();
|
Thread.yield();
|
||||||
}
|
}
|
||||||
updateThreadExecutor.shutdownNow();
|
updateThreadExecutor.shutdownNow();
|
||||||
|
try {
|
||||||
|
updateThreadExecutor.awaitTermination(500, TimeUnit.MILLISECONDS);
|
||||||
|
} catch( InterruptedException ex ) {
|
||||||
|
|
||||||
|
} finally {
|
||||||
// Cleanup
|
// Cleanup
|
||||||
shutdown();
|
shutdown();
|
||||||
cleanup();
|
cleanup();
|
||||||
@@ -1454,11 +1461,12 @@ public class Zeichenmaschine extends Constants {
|
|||||||
quit();
|
quit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove
|
// TODO: Remove
|
||||||
class DelayedTask implements Delayed {
|
static class DelayedTask implements Delayed {
|
||||||
|
|
||||||
long startTime; // in ms
|
long startTime; // in ms
|
||||||
|
|
||||||
@@ -1547,6 +1555,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: (ngb) exception handling when update/draw throws ex
|
||||||
class UpdateThreadExecutor extends ThreadPoolExecutor {
|
class UpdateThreadExecutor extends ThreadPoolExecutor {
|
||||||
|
|
||||||
private Thread updateThread;
|
private Thread updateThread;
|
||||||
@@ -1555,7 +1564,18 @@ public class Zeichenmaschine extends Constants {
|
|||||||
|
|
||||||
public UpdateThreadExecutor() {
|
public UpdateThreadExecutor() {
|
||||||
super(1, 1, 0L,
|
super(1, 1, 0L,
|
||||||
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
|
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
|
||||||
|
new ThreadFactory() {
|
||||||
|
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||||
|
@Override
|
||||||
|
public Thread newThread( Runnable r ) {
|
||||||
|
Thread t = new Thread(mainThread.getThreadGroup(), r,
|
||||||
|
"updateThread-" + threadNumber.getAndIncrement(),
|
||||||
|
0);
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
});
|
||||||
updateState = Options.AppState.IDLE;
|
updateState = Options.AppState.IDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public abstract class Animation<T> extends Constants implements Updatable {
|
|||||||
|
|
||||||
public Animation( DoubleUnaryOperator easing ) {
|
public Animation( DoubleUnaryOperator easing ) {
|
||||||
this.runtime = Constants.DEFAULT_ANIM_RUNTIME;
|
this.runtime = Constants.DEFAULT_ANIM_RUNTIME;
|
||||||
this.easing = Validator.requireNotNull(easing);
|
this.easing = Validator.requireNotNull(easing, "easing");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Animation( int runtime ) {
|
public Animation( int runtime ) {
|
||||||
@@ -34,7 +34,7 @@ public abstract class Animation<T> extends Constants implements Updatable {
|
|||||||
|
|
||||||
public Animation( int runtime, DoubleUnaryOperator easing ) {
|
public Animation( int runtime, DoubleUnaryOperator easing ) {
|
||||||
this.runtime = runtime;
|
this.runtime = runtime;
|
||||||
this.easing = Validator.requireNotNull(easing);
|
this.easing = Validator.requireNotNull(easing, "easing");
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRuntime() {
|
public int getRuntime() {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public class AnimationFacade<S> extends Animation<S> {
|
|||||||
|
|
||||||
public AnimationFacade( Animation<S> anim, int runtime, DoubleUnaryOperator easing ) {
|
public AnimationFacade( Animation<S> anim, int runtime, DoubleUnaryOperator easing ) {
|
||||||
super(runtime, easing);
|
super(runtime, easing);
|
||||||
this.anim = Validator.requireNotNull(anim);
|
this.anim = Validator.requireNotNull(anim, "anim");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ public class AnimationGroup<T> extends Animation<T> {
|
|||||||
|
|
||||||
List<Animation<T>> anims;
|
List<Animation<T>> anims;
|
||||||
|
|
||||||
|
|
||||||
private boolean overrideEasing = false;
|
private boolean overrideEasing = false;
|
||||||
|
|
||||||
private int overrideRuntime = -1;
|
private int overrideRuntime = -1;
|
||||||
|
|||||||
@@ -123,8 +123,8 @@ public class Animations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final <T> Future<T> animateProperty( T target, final double from, final double to, int runtime, DoubleUnaryOperator easing, DoubleConsumer propSetter ) {
|
public static final <T> Future<T> animateProperty( T target, final double from, final double to, int runtime, DoubleUnaryOperator easing, DoubleConsumer propSetter ) {
|
||||||
Validator.requireNotNull(target);
|
Validator.requireNotNull(target, "target");
|
||||||
Validator.requireNotNull(propSetter);
|
Validator.requireNotNull(propSetter, "propSetter");
|
||||||
return play(target, runtime, easing, ( e ) -> propSetter.accept(Constants.interpolate(from, to, e)));
|
return play(target, runtime, easing, ( e ) -> propSetter.accept(Constants.interpolate(from, to, e)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
12
src/main/java/schule/ngb/zm/anim/package-info.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Klassen zur Animation von
|
||||||
|
* {@link schule.ngb.zm.shapes.Shape} Objekten auf einem
|
||||||
|
* {@link schule.ngb.zm.layers.ShapesLayer}.
|
||||||
|
* <p>
|
||||||
|
* Mit den Animationsklassen lassen sich neben {@code Shape} Objekten aber auch
|
||||||
|
* andere Objekte animieren.
|
||||||
|
* <p>
|
||||||
|
* Das Paket setzt auf den funktionalen Programmierschnittstellen von Java auf
|
||||||
|
* und kann als Einführung in das Paradigma dienen.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.anim;
|
||||||
@@ -11,9 +11,13 @@ import java.awt.RadialGradientPaint;
|
|||||||
/**
|
/**
|
||||||
* Eine Ebene, die nur aus einer Farbe (oder einem Farbverlauf) besteht.
|
* Eine Ebene, die nur aus einer Farbe (oder einem Farbverlauf) besteht.
|
||||||
* <p>
|
* <p>
|
||||||
|
* Ein {@code ColorLayer} ist eine der drei Standardebenen der
|
||||||
|
* {@link schule.ngb.zm.Zeichenmaschine}.
|
||||||
|
* <p>
|
||||||
* Die Farbe der Ebene kann beliebig gesetzt werden und kann gut als
|
* Die Farbe der Ebene kann beliebig gesetzt werden und kann gut als
|
||||||
* Hintergundfarbe für eine Szene dienen, oder als halbtransparente "Abdeckung",
|
* Hintergrundfarbe für eine Szene dienen, oder als halbtransparente
|
||||||
* wenn ein {@code ColorLayer} über den anderen Ebenen eingefügt wird.
|
* "Abdeckung", wenn ein {@code ColorLayer} über den anderen Ebenen eingefügt
|
||||||
|
* wird.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings( "unused" )
|
@SuppressWarnings( "unused" )
|
||||||
public class ColorLayer extends Layer {
|
public class ColorLayer extends Layer {
|
||||||
@@ -53,9 +57,6 @@ public class ColorLayer extends Layer {
|
|||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setSize( int width, int height ) {
|
public void setSize( int width, int height ) {
|
||||||
super.setSize(width, height);
|
super.setSize(width, height);
|
||||||
@@ -63,16 +64,14 @@ public class ColorLayer extends Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die Hintergrundfarbe der Ebene zurück.
|
* @return Die aktuelle Hintergrundfarbe der Ebene.
|
||||||
*
|
|
||||||
* @return Die aktuelle Hintergrundfarbe.
|
|
||||||
*/
|
*/
|
||||||
public Color getColor() {
|
public Color getColor() {
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt die Farbe der Ebene neu.
|
* Setzt die Farbe der Ebene auf die angegebene Farbe.
|
||||||
*
|
*
|
||||||
* @param color Die neue Hintergrundfarbe.
|
* @param color Die neue Hintergrundfarbe.
|
||||||
*/
|
*/
|
||||||
@@ -82,22 +81,69 @@ public class ColorLayer extends Layer {
|
|||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Farbe der Ebene auf einen Grauwert mit der angegebenen
|
||||||
|
* Intensität. 0 entspricht schwarz, 255 entspricht weiß.
|
||||||
|
*
|
||||||
|
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||||
|
* @see Color#Color(int)
|
||||||
|
*/
|
||||||
public void setColor( int gray ) {
|
public void setColor( int gray ) {
|
||||||
setColor(gray, gray, gray, 255);
|
setColor(gray, gray, gray, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Farbe der Ebene auf einen Grauwert mit der angegebenen
|
||||||
|
* Intensität und dem angegebenen Transparenzwert. Der Grauwert 0 entspricht
|
||||||
|
* schwarz, 255 entspricht weiß.
|
||||||
|
*
|
||||||
|
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||||
|
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||||
|
* @see Color#Color(int, int)
|
||||||
|
*/
|
||||||
public void setColor( int gray, int alpha ) {
|
public void setColor( int gray, int alpha ) {
|
||||||
setColor(gray, gray, gray, alpha);
|
setColor(gray, gray, gray, alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Farbe der Ebene auf die Farbe mit den angegebenen Rot-, Grün-
|
||||||
|
* und Blauanteilen.
|
||||||
|
*
|
||||||
|
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
||||||
|
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
||||||
|
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
||||||
|
* @see Color#Color(int, int, int)
|
||||||
|
* @see <a
|
||||||
|
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||||
|
*/
|
||||||
public void setColor( int red, int green, int blue ) {
|
public void setColor( int red, int green, int blue ) {
|
||||||
setColor(red, green, blue, 255);
|
setColor(red, green, blue, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Farbe der Ebene auf die Farbe mit den angegebenen Rot-, Grün-
|
||||||
|
* und Blauanteilen und dem angegebenen Transparenzwert.
|
||||||
|
*
|
||||||
|
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
||||||
|
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
||||||
|
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
||||||
|
* @param alpha Ein Transparenzwert zwischen 0 und 25
|
||||||
|
* @see Color#Color(int, int, int, int)
|
||||||
|
* @see <a
|
||||||
|
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||||
|
*/
|
||||||
public void setColor( int red, int green, int blue, int alpha ) {
|
public void setColor( int red, int green, int blue, int alpha ) {
|
||||||
setColor(new Color(red, green, blue, alpha));
|
setColor(new Color(red, green, blue, alpha));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllung der Ebene auf einen linearen Farbverlauf, der in die
|
||||||
|
* angegebene Richtung verläuft.
|
||||||
|
*
|
||||||
|
* @param from Farbe am Startpunkt.
|
||||||
|
* @param to Farbe am Endpunkt.
|
||||||
|
* @param dir Richtung des Farbverlaufs.
|
||||||
|
*/
|
||||||
public void setGradient( Color from, Color to, Options.Direction dir ) {
|
public void setGradient( Color from, Color to, Options.Direction dir ) {
|
||||||
double halfW = getWidth() * .5;
|
double halfW = getWidth() * .5;
|
||||||
double halfH = getHeight() * .5;
|
double halfH = getHeight() * .5;
|
||||||
@@ -112,6 +158,18 @@ public class ColorLayer extends Layer {
|
|||||||
setGradient(fromX, fromY, from, toX, toY, to);
|
setGradient(fromX, fromY, from, toX, toY, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllung der Ebene auf einen linearen Farbverlauf, der am Punkt
|
||||||
|
* ({@code fromX}, {@code fromY}) mit der Farbe {@code from} startet und am
|
||||||
|
* Punkt (({@code toX}, {@code toY}) mit der Farbe {@code to} endet.
|
||||||
|
*
|
||||||
|
* @param fromX x-Koordinate des Startpunktes.
|
||||||
|
* @param fromY y-Koordinate des Startpunktes.
|
||||||
|
* @param from Farbe am Startpunkt.
|
||||||
|
* @param toX x-Koordinate des Endpunktes.
|
||||||
|
* @param toY y-Koordinate des Endpunktes.
|
||||||
|
* @param to Farbe am Endpunkt.
|
||||||
|
*/
|
||||||
public void setGradient( double fromX, double fromY, Color from, double toX, double toY, Color to ) {
|
public void setGradient( double fromX, double fromY, Color from, double toX, double toY, Color to ) {
|
||||||
this.color = from;
|
this.color = from;
|
||||||
background = new GradientPaint(
|
background = new GradientPaint(
|
||||||
@@ -121,10 +179,30 @@ public class ColorLayer extends Layer {
|
|||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllung der Ebene auf einen kreisförmigen (radialen)
|
||||||
|
* Farbverlauf, der im Zentrum beginnt.
|
||||||
|
*
|
||||||
|
* @param from Farbe im Zentrum.
|
||||||
|
* @param to Farbe am Rand.
|
||||||
|
*/
|
||||||
public void setGradient( Color from, Color to ) {
|
public void setGradient( Color from, Color to ) {
|
||||||
setGradient(getWidth() * .5, getHeight() * .5, Math.min(getWidth() * .5, getHeight() * .5), from, to);
|
setGradient(getWidth() * .5, getHeight() * .5, Math.min(getWidth() * .5, getHeight() * .5), from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllung der Ebene auf einen kreisförmigen (radialen)
|
||||||
|
* Farbverlauf, mit dem Zentrum im Punkt ({@code centerX}, {@code centerY})
|
||||||
|
* und dem angegebenen Radius. Der Verlauf starte im Zentrum mit der Farbe
|
||||||
|
* {@code from} und endet am Rand des durch den Radius beschriebenen Kreises
|
||||||
|
* mit der Farbe {@code to}.
|
||||||
|
*
|
||||||
|
* @param centerX x-Koordinate des Kreismittelpunktes.
|
||||||
|
* @param centerY y-Koordinate des Kreismittelpunktes.
|
||||||
|
* @param radius Radius des Kreises.
|
||||||
|
* @param from Farbe im Zentrum des Kreises.
|
||||||
|
* @param to Farbe am Rand des Kreises.
|
||||||
|
*/
|
||||||
public void setGradient( double centerX, double centerY, double radius, Color from, Color to ) {
|
public void setGradient( double centerX, double centerY, double radius, Color from, Color to ) {
|
||||||
this.color = from;
|
this.color = from;
|
||||||
background = new RadialGradientPaint(
|
background = new RadialGradientPaint(
|
||||||
@@ -134,6 +212,9 @@ public class ColorLayer extends Layer {
|
|||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet den Hintergrund der Ebene mit der gesetzten Füllung neu.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
drawing.setPaint(background);
|
drawing.setPaint(background);
|
||||||
|
|||||||
@@ -8,55 +8,99 @@ import java.util.Collections;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein Layer um {@link Drawable} Objekte zu zeichnen.
|
||||||
|
* <p>
|
||||||
|
* Objekte, die das {@code Drawable} Interface implementieren, können der Ebene
|
||||||
|
* hinzugefügt werden. Die Ebene sorgt dafür, dass alle {@code Drawable}s einmal
|
||||||
|
* pro Frame über ihre {@link Drawable#draw(Graphics2D)} Methode gezeichnet.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public class DrawableLayer extends Layer {
|
public class DrawableLayer extends Layer {
|
||||||
|
|
||||||
protected List<Drawable> drawables = new LinkedList<>();
|
/**
|
||||||
|
* Liste der {@link Drawable}s.
|
||||||
|
*/
|
||||||
|
protected final List<Drawable> drawables;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob die Ebene bei jedem Aufruf von {@link #draw(Graphics2D)} geleert
|
||||||
|
* werden soll.
|
||||||
|
*/
|
||||||
protected boolean clearBeforeDraw = true;
|
protected boolean clearBeforeDraw = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt eine Ebene in der Standardgröße.
|
||||||
|
*/
|
||||||
public DrawableLayer() {
|
public DrawableLayer() {
|
||||||
|
drawables = new LinkedList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt eine Ebene mit der angegebenen Größe.
|
||||||
|
*
|
||||||
|
* @param width Die Breite der Ebene.
|
||||||
|
* @param height Die Höhe der Ebene.
|
||||||
|
*/
|
||||||
public DrawableLayer( int width, int height ) {
|
public DrawableLayer( int width, int height ) {
|
||||||
super(width, height);
|
super(width, height);
|
||||||
|
drawables = new LinkedList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fügt alle angegebenen {@code Drawable}s der Ebene hinzu.
|
||||||
|
*
|
||||||
|
* @param drawables Die {@code Drawable} Objekte.
|
||||||
|
*/
|
||||||
public void add( Drawable... drawables ) {
|
public void add( Drawable... drawables ) {
|
||||||
synchronized( drawables ) {
|
synchronized( this.drawables ) {
|
||||||
for( Drawable d : drawables ) {
|
Collections.addAll(this.drawables, drawables);
|
||||||
this.drawables.add(d);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt eine Liste aller {@code Drawable} Objekte dieser Ebene zurück.
|
||||||
|
*
|
||||||
|
* @return Die Liste der {@code Drawable} Objekte.
|
||||||
|
*/
|
||||||
public java.util.List<Drawable> getDrawables() {
|
public java.util.List<Drawable> getDrawables() {
|
||||||
return drawables;
|
return drawables;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob die Ebene bei jedem Frame automatisch gelöscht wird.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn die Ebene vorm Zeichnen gelöscht wird,
|
||||||
|
* {@code false} sonst.
|
||||||
|
*/
|
||||||
public boolean isClearBeforeDraw() {
|
public boolean isClearBeforeDraw() {
|
||||||
return clearBeforeDraw;
|
return clearBeforeDraw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stellt ein, ob die Ebene vorm Zeichnen gelöscht werden soll.
|
||||||
|
*
|
||||||
|
* @param pClearBeforeDraw Ob die Ebene vorm Zeichnen gelöscht werden
|
||||||
|
* soll.
|
||||||
|
*/
|
||||||
public void setClearBeforeDraw( boolean pClearBeforeDraw ) {
|
public void setClearBeforeDraw( boolean pClearBeforeDraw ) {
|
||||||
this.clearBeforeDraw = pClearBeforeDraw;
|
this.clearBeforeDraw = pClearBeforeDraw;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw( Graphics2D pGraphics ) {
|
public void draw( Graphics2D graphics ) {
|
||||||
if( clearBeforeDraw ) {
|
if( clearBeforeDraw ) {
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized( drawables ) {
|
|
||||||
List<Drawable> it = List.copyOf(drawables);
|
List<Drawable> it = List.copyOf(drawables);
|
||||||
for( Drawable d : it ) {
|
for( Drawable d : it ) {
|
||||||
if( d.isVisible() ) {
|
if( d.isVisible() ) {
|
||||||
d.draw(drawing);
|
d.draw(drawing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
super.draw(pGraphics);
|
super.draw(graphics);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +1,126 @@
|
|||||||
package schule.ngb.zm.layers;
|
package schule.ngb.zm.layers;
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.Image;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Layer;
|
import schule.ngb.zm.Layer;
|
||||||
import schule.ngb.zm.util.io.ImageLoader;
|
import schule.ngb.zm.util.io.ImageLoader;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Image;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine Ebene, die ein statisches Bild anzeigt.
|
||||||
|
* <p>
|
||||||
|
* Die Ebene wird mit einem Bild initialisiert und zeigt dieses Bild als
|
||||||
|
* einzigen Inhalt an. Optional kann die Position des Bildes verändert werden,
|
||||||
|
* sodass es nicht im Ursprung der Ebene gezeichnet wird.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public class ImageLayer extends Layer {
|
public class ImageLayer extends Layer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Das Bild, das angezeigt wird.
|
||||||
|
*/
|
||||||
protected Image image;
|
protected Image image;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* x-Koordinate der oberen linken Ecke auf der Ebene.
|
||||||
|
*/
|
||||||
protected double x = 0;
|
protected double x = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* y-Koordinate der oberen linken Ecke auf der Ebene.
|
||||||
|
*/
|
||||||
protected double y = 0;
|
protected double y = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interner Schalter, ob die Ebene neu gezeichnet werden muss.
|
||||||
|
*/
|
||||||
protected boolean redraw = true;
|
protected boolean redraw = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt eine Bildebene in der Standardgröße aus der angegebenen
|
||||||
|
* Bildquelle.
|
||||||
|
*
|
||||||
|
* @param source Eine Bildquelle.
|
||||||
|
* @see ImageLoader#loadImage(String)
|
||||||
|
*/
|
||||||
public ImageLayer( String source ) {
|
public ImageLayer( String source ) {
|
||||||
image = ImageLoader.loadImage(source);
|
image = ImageLoader.loadImage(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt eine Bildebene in der Standardgröße aus dem angegebenen Bild.
|
||||||
|
*
|
||||||
|
* @param image Ein Bild-Objekt.
|
||||||
|
*/
|
||||||
public ImageLayer( Image image ) {
|
public ImageLayer( Image image ) {
|
||||||
this.image = image;
|
this.image = image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt eine Bildebene in der angegebenen Größe aus dem angegebenen
|
||||||
|
* Bild.
|
||||||
|
*
|
||||||
|
* @param width Breite der Bildebene.
|
||||||
|
* @param height Höhe der Bildebene.
|
||||||
|
* @param image Ein Bild-Objekt.
|
||||||
|
*/
|
||||||
public ImageLayer( int width, int height, Image image ) {
|
public ImageLayer( int width, int height, Image image ) {
|
||||||
super(width, height);
|
super(width, height);
|
||||||
this.image = image;
|
this.image = image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt das Bild der Ebene auf das angegebene Bild-Objekt.
|
||||||
|
*
|
||||||
|
* @param image Ein Bild-Objekt.
|
||||||
|
*/
|
||||||
public void setImage( Image image ) {
|
public void setImage( Image image ) {
|
||||||
this.image = image;
|
this.image = image;
|
||||||
redraw = true;
|
redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Die x-Koordinate des Bildes in der Ebene.
|
||||||
|
*/
|
||||||
public double getX() {
|
public double getX() {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die {@code x}-Koordinate des BIldes in der Ebene auf den
|
||||||
|
* angegebenen Wert.
|
||||||
|
*
|
||||||
|
* @param pX Die x-Koordinate des Bildes.
|
||||||
|
*/
|
||||||
public void setX( double pX ) {
|
public void setX( double pX ) {
|
||||||
this.x = pX;
|
this.x = pX;
|
||||||
redraw = true;
|
redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Die y-Koordinate des Bildes in der Ebene.
|
||||||
|
*/
|
||||||
public double getY() {
|
public double getY() {
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die {@code y}-Koordinate des BIldes in der Ebene auf den
|
||||||
|
* angegebenen Wert.
|
||||||
|
*
|
||||||
|
* @param pY Die y-Koordinate des Bildes.
|
||||||
|
*/
|
||||||
public void setY( double pY ) {
|
public void setY( double pY ) {
|
||||||
this.y = pY;
|
this.y = pY;
|
||||||
redraw = true;
|
redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Löscht die Ebene und zeichnet das Bild neu.
|
||||||
|
* <p>
|
||||||
|
* In der Regel muss die Ebene nicht gelöscht werden, da sie automatisch neu
|
||||||
|
* gezeichnet wird, sobald sich das zugrundeliegende Bild ändert.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
super.clear();
|
super.clear();
|
||||||
|
|||||||
@@ -10,13 +10,23 @@ import java.awt.Graphics2D;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.DoubleUnaryOperator;
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine Ebene um {@link Shape} Objekte zu zeichnen.
|
||||||
|
* <p>
|
||||||
|
* Ein {@code ShapesLayer} ist eine der drei Standardebenen der
|
||||||
|
* {@link schule.ngb.zm.Zeichenmaschine}.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public class ShapesLayer extends Layer {
|
public class ShapesLayer extends Layer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
protected boolean clearBeforeDraw = true;
|
protected boolean clearBeforeDraw = true;
|
||||||
|
|
||||||
private List<Shape> shapes;
|
private final List<Shape> shapes;
|
||||||
|
|
||||||
private List<Animation<? extends Shape>> animations;
|
private final List<Animation<? extends Shape>> animations;
|
||||||
|
|
||||||
public ShapesLayer() {
|
public ShapesLayer() {
|
||||||
super();
|
super();
|
||||||
@@ -37,7 +47,7 @@ public class ShapesLayer extends Layer {
|
|||||||
public <ST extends Shape> ST getShape( Class<ST> shapeClass ) {
|
public <ST extends Shape> ST getShape( Class<ST> shapeClass ) {
|
||||||
for( Shape s : shapes ) {
|
for( Shape s : shapes ) {
|
||||||
if( shapeClass.isInstance(s) ) {
|
if( shapeClass.isInstance(s) ) {
|
||||||
return (ST) s;
|
return shapeClass.cast(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -51,7 +61,7 @@ public class ShapesLayer extends Layer {
|
|||||||
List<ST> result = new LinkedList<>();
|
List<ST> result = new LinkedList<>();
|
||||||
for( Shape s : shapes ) {
|
for( Shape s : shapes ) {
|
||||||
if( shapeClass.isInstance(s) ) {
|
if( shapeClass.isInstance(s) ) {
|
||||||
result.add((ST) s);
|
result.add(shapeClass.cast(s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -59,17 +69,13 @@ public class ShapesLayer extends Layer {
|
|||||||
|
|
||||||
public void add( Shape... shapes ) {
|
public void add( Shape... shapes ) {
|
||||||
synchronized( this.shapes ) {
|
synchronized( this.shapes ) {
|
||||||
for( Shape s : shapes ) {
|
Collections.addAll(this.shapes, shapes);
|
||||||
this.shapes.add(s);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add( Collection<Shape> shapes ) {
|
public void add( Collection<Shape> shapes ) {
|
||||||
synchronized( this.shapes ) {
|
synchronized( this.shapes ) {
|
||||||
for( Shape s : shapes ) {
|
this.shapes.addAll(shapes);
|
||||||
this.shapes.add(s);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,9 +89,7 @@ public class ShapesLayer extends Layer {
|
|||||||
|
|
||||||
public void remove( Collection<Shape> shapes ) {
|
public void remove( Collection<Shape> shapes ) {
|
||||||
synchronized( this.shapes ) {
|
synchronized( this.shapes ) {
|
||||||
for( Shape s : shapes ) {
|
this.shapes.removeAll(shapes);
|
||||||
this.shapes.remove(s);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,8 +120,8 @@ public class ShapesLayer extends Layer {
|
|||||||
anim.start();
|
anim.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
public void play( Animation<? extends Shape>... anims ) {
|
public final void play( Animation<? extends Shape>... anims ) {
|
||||||
for( Animation<? extends Shape> anim : anims ) {
|
for( Animation<? extends Shape> anim : anims ) {
|
||||||
this.animations.add(anim);
|
this.animations.add(anim);
|
||||||
anim.start();
|
anim.start();
|
||||||
@@ -141,13 +145,13 @@ public class ShapesLayer extends Layer {
|
|||||||
anim.update(delta);
|
anim.update(delta);
|
||||||
|
|
||||||
if( !anim.isActive() ) {
|
if( !anim.isActive() ) {
|
||||||
animations.remove(anim);
|
it.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw( Graphics2D pGraphics ) {
|
public void draw( Graphics2D graphics ) {
|
||||||
if( clearBeforeDraw ) {
|
if( clearBeforeDraw ) {
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
@@ -161,7 +165,7 @@ public class ShapesLayer extends Layer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.draw(pGraphics);
|
super.draw(graphics);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
package schule.ngb.zm.layers;
|
package schule.ngb.zm.layers;
|
||||||
|
|
||||||
|
import schule.ngb.zm.*;
|
||||||
import schule.ngb.zm.Color;
|
import schule.ngb.zm.Color;
|
||||||
import schule.ngb.zm.Layer;
|
import schule.ngb.zm.Fillable;
|
||||||
import schule.ngb.zm.Options;
|
import schule.ngb.zm.Strokeable;
|
||||||
import schule.ngb.zm.Vector;
|
|
||||||
import schule.ngb.zm.shapes.FilledShape;
|
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
public class TurtleLayer extends Layer {
|
/**
|
||||||
|
* Eine Ebene, auf der eine Turtle gesteuert werden kann, die Grafiken plottet.
|
||||||
|
* <p>
|
||||||
|
* Die Turtle verhält sich ähnlich zu ihren Entsprechungen in Logo oder
|
||||||
|
* TigerJython.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
|
public class TurtleLayer extends Layer implements Strokeable, Fillable {
|
||||||
|
|
||||||
// Rotating by the clock
|
// Rotating by the clock
|
||||||
public static final int H1 = 30;
|
public static final int H1 = 30;
|
||||||
@@ -39,7 +46,7 @@ public class TurtleLayer extends Layer {
|
|||||||
|
|
||||||
public static final int H12 = 360;
|
public static final int H12 = 360;
|
||||||
|
|
||||||
private static Stack<Color> turtleColors;
|
private final static Stack<Color> turtleColors;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
turtleColors = new Stack<>();
|
turtleColors = new Stack<>();
|
||||||
@@ -51,9 +58,9 @@ public class TurtleLayer extends Layer {
|
|||||||
turtleColors.add(Color.BLUE);
|
turtleColors.add(Color.BLUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Turtle mainTurtle = null;
|
private final Turtle mainTurtle;
|
||||||
|
|
||||||
private ArrayList<Turtle> turtles = new ArrayList<Turtle>(6);
|
private final List<Turtle> turtles = new ArrayList<>(6);
|
||||||
|
|
||||||
public TurtleLayer() {
|
public TurtleLayer() {
|
||||||
super();
|
super();
|
||||||
@@ -117,7 +124,20 @@ public class TurtleLayer extends Layer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin of delegate methods (auto-generated)
|
// begin of delegate methods (auto-generated)
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isVisible() {
|
||||||
|
return mainTurtle.isVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beginPath() {
|
||||||
|
mainTurtle.beginPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closePath() {
|
||||||
|
mainTurtle.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
public void fill() {
|
public void fill() {
|
||||||
mainTurtle.fill();
|
mainTurtle.fill();
|
||||||
@@ -175,100 +195,196 @@ public class TurtleLayer extends Layer {
|
|||||||
mainTurtle.moveTo(x, y);
|
mainTurtle.moveTo(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Color getFillColor() {
|
@Override
|
||||||
return mainTurtle.getFillColor();
|
public void setFill( Paint fill ) {
|
||||||
|
mainTurtle.setFill(fill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Paint getFill() {
|
||||||
|
return mainTurtle.getFill();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setFillColor( Color color ) {
|
public void setFillColor( Color color ) {
|
||||||
mainTurtle.setFillColor(color);
|
mainTurtle.setFillColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFillColor( int gray ) {
|
@Override
|
||||||
mainTurtle.setFillColor(gray);
|
public Color getFillColor() {
|
||||||
}
|
return mainTurtle.getFillColor();
|
||||||
|
|
||||||
public void noFill() {
|
|
||||||
mainTurtle.noFill();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( int gray, int alpha ) {
|
|
||||||
mainTurtle.setFillColor(gray, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( int red, int green, int blue ) {
|
|
||||||
mainTurtle.setFillColor(red, green, blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
|
||||||
mainTurtle.setFillColor(red, green, blue, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetFill() {
|
|
||||||
mainTurtle.resetFill();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Color getStrokeColor() {
|
|
||||||
return mainTurtle.getStrokeColor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setStrokeColor( Color color ) {
|
public void setStrokeColor( Color color ) {
|
||||||
mainTurtle.setStrokeColor(color);
|
mainTurtle.setStrokeColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStrokeColor( int gray ) {
|
@Override
|
||||||
mainTurtle.setStrokeColor(gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void noStroke() {
|
|
||||||
mainTurtle.noStroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( int gray, int alpha ) {
|
|
||||||
mainTurtle.setStrokeColor(gray, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( int red, int green, int blue ) {
|
|
||||||
mainTurtle.setStrokeColor(red, green, blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
|
||||||
mainTurtle.setStrokeColor(red, green, blue, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getStrokeWeight() {
|
|
||||||
return mainTurtle.getStrokeWeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeWeight( double weight ) {
|
public void setStrokeWeight( double weight ) {
|
||||||
mainTurtle.setStrokeWeight(weight);
|
mainTurtle.setStrokeWeight(weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Options.StrokeType getStrokeType() {
|
public Options.StrokeType getStrokeType() {
|
||||||
return mainTurtle.getStrokeType();
|
return mainTurtle.getStrokeType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setStrokeType( Options.StrokeType type ) {
|
public void setStrokeType( Options.StrokeType type ) {
|
||||||
mainTurtle.setStrokeType(type);
|
mainTurtle.setStrokeType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGradient( Color from, Color to, Options.Direction dir ) {
|
||||||
|
mainTurtle.setGradient(from, to, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGradient( Color from, Color to ) {
|
||||||
|
mainTurtle.setGradient(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFill() {
|
||||||
|
return mainTurtle.hasFill();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFillColor() {
|
||||||
|
return mainTurtle.hasFillColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasGradient() {
|
||||||
|
return mainTurtle.hasGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFillColor( Color color, int alpha ) {
|
||||||
|
mainTurtle.setFillColor(color, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFillColor( int gray ) {
|
||||||
|
mainTurtle.setFillColor(gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFillColor( int gray, int alpha ) {
|
||||||
|
mainTurtle.setFillColor(gray, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFillColor( int red, int green, int blue ) {
|
||||||
|
mainTurtle.setFillColor(red, green, blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||||
|
mainTurtle.setFillColor(red, green, blue, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void noFill() {
|
||||||
|
mainTurtle.noFill();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetFill() {
|
||||||
|
mainTurtle.resetFill();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MultipleGradientPaint getGradient() {
|
||||||
|
return mainTurtle.getGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGradient( double fromX, double fromY, Color from, double toX, double toY, Color to ) {
|
||||||
|
mainTurtle.setGradient(fromX, fromY, from, toX, toY, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGradient( double centerX, double centerY, double radius, Color from, Color to ) {
|
||||||
|
mainTurtle.setGradient(centerX, centerY, radius, from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void noGradient() {
|
||||||
|
mainTurtle.noGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStroke( Stroke stroke ) {
|
||||||
|
mainTurtle.setStroke(stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stroke getStroke() {
|
||||||
|
return mainTurtle.getStroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasStroke() {
|
||||||
|
return mainTurtle.hasStroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Color getStrokeColor() {
|
||||||
|
return mainTurtle.getStrokeColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStrokeColor( Color color, int alpha ) {
|
||||||
|
mainTurtle.setStrokeColor(color, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStrokeColor( int gray ) {
|
||||||
|
mainTurtle.setStrokeColor(gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStrokeColor( int gray, int alpha ) {
|
||||||
|
mainTurtle.setStrokeColor(gray, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStrokeColor( int red, int green, int blue ) {
|
||||||
|
mainTurtle.setStrokeColor(red, green, blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||||
|
mainTurtle.setStrokeColor(red, green, blue, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void noStroke() {
|
||||||
|
mainTurtle.noStroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void resetStroke() {
|
public void resetStroke() {
|
||||||
mainTurtle.resetStroke();
|
mainTurtle.resetStroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPosToPath() {
|
@Override
|
||||||
mainTurtle.addPosToPath();
|
public double getStrokeWeight() {
|
||||||
|
return mainTurtle.getStrokeWeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void closePath() {
|
|
||||||
mainTurtle.closePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
// End of delegate methods (auto-generated)
|
// end of delegate methods (auto-generated)
|
||||||
|
|
||||||
|
|
||||||
public class Turtle extends FilledShape {
|
/**
|
||||||
|
* Die Turtle der Zeichenmaschine.
|
||||||
|
*/
|
||||||
|
public class Turtle extends BasicDrawable {
|
||||||
|
|
||||||
private static final int STD_SIZE = 12;
|
private static final int DEFAULT_SIZE = 12;
|
||||||
|
|
||||||
boolean penDown = true;
|
boolean penDown = true;
|
||||||
|
|
||||||
@@ -282,7 +398,31 @@ public class TurtleLayer extends Layer {
|
|||||||
|
|
||||||
boolean pathOpen = false;
|
boolean pathOpen = false;
|
||||||
|
|
||||||
Turtle() {}
|
/**
|
||||||
|
* Path-Objekt für die Darstellung der Turtle.
|
||||||
|
*/
|
||||||
|
Path2D.Double turtlePath;
|
||||||
|
|
||||||
|
Turtle() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVisible() {
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beginPath() {
|
||||||
|
pathOpen = false;
|
||||||
|
addPosToPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closePath() {
|
||||||
|
if( pathOpen ) {
|
||||||
|
addPosToPath();
|
||||||
|
path.closePath();
|
||||||
|
path.trimToSize();
|
||||||
|
pathOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void addPosToPath() {
|
private void addPosToPath() {
|
||||||
if( !pathOpen ) {
|
if( !pathOpen ) {
|
||||||
@@ -294,53 +434,39 @@ public class TurtleLayer extends Layer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closePath() {
|
|
||||||
if( pathOpen ) {
|
|
||||||
addPosToPath();
|
|
||||||
path.closePath();
|
|
||||||
path.trimToSize();
|
|
||||||
pathOpen = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fill() {
|
public void fill() {
|
||||||
closePath();
|
closePath();
|
||||||
|
|
||||||
if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
if( hasFill() ) {
|
||||||
drawing.setColor(fillColor.getJavaColor());
|
drawing.setPaint(getFill());
|
||||||
drawing.fill(path);
|
drawing.fill(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isVisible() {
|
|
||||||
return visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw( Graphics2D graphics ) {
|
public void draw( Graphics2D graphics ) {
|
||||||
/*Shape shape = new RoundRectangle2D.Double(
|
if( turtlePath == null ) {
|
||||||
-12, -5, 16, 10, 5, 3
|
turtlePath = new Path2D.Double();
|
||||||
);*/
|
path.moveTo(DEFAULT_SIZE, 0);
|
||||||
Path2D path = new Path2D.Double();
|
path.lineTo(-DEFAULT_SIZE, -DEFAULT_SIZE / 2.0);
|
||||||
path.moveTo(STD_SIZE, 0);
|
path.lineTo(-DEFAULT_SIZE, DEFAULT_SIZE / 2.0);
|
||||||
path.lineTo(-STD_SIZE, -STD_SIZE/2);
|
path.lineTo(DEFAULT_SIZE, 0);
|
||||||
path.lineTo(-STD_SIZE, STD_SIZE/2);
|
}
|
||||||
path.lineTo(STD_SIZE, 0);
|
|
||||||
|
|
||||||
AffineTransform verzerrung = new AffineTransform();
|
AffineTransform verzerrung = new AffineTransform();
|
||||||
verzerrung.translate(position.x, position.y);
|
verzerrung.translate(position.x, position.y);
|
||||||
verzerrung.rotate(Math.toRadians(direction.angle()));
|
verzerrung.rotate(Math.toRadians(direction.angle()));
|
||||||
|
|
||||||
Shape shape = verzerrung.createTransformedShape(path);
|
java.awt.Shape shape = verzerrung.createTransformedShape(turtlePath);
|
||||||
|
|
||||||
if( strokeColor != null ) {
|
if( hasStroke() ) {
|
||||||
graphics.setColor(strokeColor.getJavaColor());
|
graphics.setColor(strokeColor.getJavaColor());
|
||||||
} else {
|
} else {
|
||||||
graphics.setColor(DEFAULT_STROKECOLOR.getJavaColor());
|
graphics.setColor(DEFAULT_STROKECOLOR.getJavaColor());
|
||||||
}
|
}
|
||||||
graphics.fill(shape);
|
graphics.fill(shape);
|
||||||
graphics.setColor(Color.BLACK.getJavaColor());
|
graphics.setColor(Color.BLACK.getJavaColor());
|
||||||
graphics.setStroke(createStroke());
|
graphics.setStroke(getStroke());
|
||||||
graphics.draw(shape);
|
graphics.draw(shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,9 +480,9 @@ public class TurtleLayer extends Layer {
|
|||||||
Vector positionStart = position.copy();
|
Vector positionStart = position.copy();
|
||||||
position.add(Vector.setLength(direction, dist));
|
position.add(Vector.setLength(direction, dist));
|
||||||
|
|
||||||
if( penDown && strokeColor != null ) {
|
if( penDown && hasStroke() ) {
|
||||||
drawing.setColor(strokeColor.getJavaColor());
|
drawing.setColor(strokeColor.getJavaColor());
|
||||||
drawing.setStroke(createStroke());
|
drawing.setStroke(getStroke());
|
||||||
drawing.drawLine((int) positionStart.x, (int) positionStart.y, (int) position.x, (int) position.y);
|
drawing.drawLine((int) positionStart.x, (int) positionStart.y, (int) position.x, (int) position.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -407,9 +533,9 @@ public class TurtleLayer extends Layer {
|
|||||||
position.x = x;
|
position.x = x;
|
||||||
position.y = y;
|
position.y = y;
|
||||||
|
|
||||||
if( penDown && strokeColor != null ) {
|
if( penDown && hasStroke() ) {
|
||||||
drawing.setColor(strokeColor.getJavaColor());
|
drawing.setColor(strokeColor.getJavaColor());
|
||||||
drawing.setStroke(createStroke());
|
drawing.setStroke(getStroke());
|
||||||
drawing.drawLine((int) x, (int) y, (int) position.x, (int) position.y);
|
drawing.drawLine((int) x, (int) y, (int) position.x, (int) position.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/main/java/schule/ngb/zm/layers/package-info.java
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält implementationen der abstrakten
|
||||||
|
* {@link schule.ngb.zm.Layer} Klasse.
|
||||||
|
* <p>
|
||||||
|
* {@code Layer} sind Ebenen, die der {@link schule.ngb.zm.Zeichenleinwand}
|
||||||
|
* hinzugefügt und pro Frame gerendert werden.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.layers;
|
||||||
@@ -1,10 +1,21 @@
|
|||||||
package schule.ngb.zm.media;
|
package schule.ngb.zm.media;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface für Audio-Medien.
|
* Schnittstelle für Audio-Medien.
|
||||||
|
*
|
||||||
|
* <h2>MP3-Dateien verwenden</h2>
|
||||||
|
* Java kann nativ nur Waveform ({@code .wav}) Dateien wiedergeben. Um auch
|
||||||
|
* MP3-Dateien zu nutzen, müssen die Bibliotheken <a href="#">jlayer</a>, <a
|
||||||
|
* href="#">tritonus-share</a> und <a href="#">mp3spi</a> eingebunden werden.
|
||||||
|
* Details zur Verwendung können in der <a
|
||||||
|
* href="https://zeichenmaschine.xyz/installation/#unterstutzung-fur-mp3">Dokumentation
|
||||||
|
* der Zeichenmaschine</a> nachgelesen werden.
|
||||||
*/
|
*/
|
||||||
public interface Audio {
|
public interface Audio {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Die Quelle, aus der das Medium geladen wurde.
|
||||||
|
*/
|
||||||
String getSource();
|
String getSource();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,7 +28,7 @@ public interface Audio {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Prüft, ob das Medium gerade in einer Schleife abgespielt wird. Wenn
|
* Prüft, ob das Medium gerade in einer Schleife abgespielt wird. Wenn
|
||||||
* {@code isLooping() == true}, dann muss auch immer
|
* {@code isLooping() == true} gilt, dann muss auch immer
|
||||||
* {@code isPlaying() == true} gelten.
|
* {@code isPlaying() == true} gelten.
|
||||||
*
|
*
|
||||||
* @return {@code true}, wenn das Medium in einer Schleife abgespielt wird,
|
* @return {@code true}, wenn das Medium in einer Schleife abgespielt wird,
|
||||||
@@ -30,7 +41,7 @@ public interface Audio {
|
|||||||
* <p>
|
* <p>
|
||||||
* Die Lautstärke wird auf einer linearen Skale festgelegt, wobei 0 kein Ton
|
* Die Lautstärke wird auf einer linearen Skale festgelegt, wobei 0 kein Ton
|
||||||
* und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des
|
* und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des
|
||||||
* Mediums.
|
* Mediums. Negative Werte setzen die Lautstärke aud 0.
|
||||||
*
|
*
|
||||||
* @param volume Die neue Lautstärke zwischen 0 und 1.
|
* @param volume Die neue Lautstärke zwischen 0 und 1.
|
||||||
* @see <a
|
* @see <a
|
||||||
@@ -39,7 +50,7 @@ public interface Audio {
|
|||||||
void setVolume( double volume );
|
void setVolume( double volume );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die aktuelle Lautstärkeeinstellung dieses Mediums zurück.
|
* Liefert die aktuelle Lautstärke dieses Mediums.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Lautstärke wird auf einer linearen Skale angegeben, wobei 0 kein Ton
|
* Die Lautstärke wird auf einer linearen Skale angegeben, wobei 0 kein Ton
|
||||||
* und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des
|
* und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des
|
||||||
@@ -50,8 +61,16 @@ public interface Audio {
|
|||||||
double getVolume();
|
double getVolume();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Startet die Wiedergabe des Mediums und beendet die Methode. Das
|
* Startet die Wiedergabe des Mediums. Das Audio-Medium wird einmal
|
||||||
* Audio-Medium wird einmal abgespielt und stoppt dann.
|
* abgespielt und stoppt dann.
|
||||||
|
* <p>
|
||||||
|
* Die Methode beendet sofort und die Wiedergabe erfolgt im Hintergrund.
|
||||||
|
* Soll die Programmausführung erst nach Wiedergabe des Mediums fortgesetzt
|
||||||
|
* werden, sollte {@link #playAndWait()} verwendet werden.
|
||||||
|
* <p>
|
||||||
|
* Soll die Wiedergabe im Hintergrund ablaufen, aber dennoch auf das Ende
|
||||||
|
* reagiert werden, kann ein
|
||||||
|
* {@link #addAudioListener(AudioListener) AudioListener} verwendet werden.
|
||||||
*/
|
*/
|
||||||
void play();
|
void play();
|
||||||
|
|
||||||
@@ -63,21 +82,36 @@ public interface Audio {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Spielt das Medium in einer kontinuierlichen Schleife ab. Die Methode
|
* Spielt das Medium in einer kontinuierlichen Schleife ab. Die Methode
|
||||||
* startet die Wiedergabe und beendet dann direkt die Methode. Um die
|
* startet die Wiedergabe im Hintergrund und beendet dann sofort. Um die
|
||||||
* Wiedergabe zu stoppen muss {@link #stop()} aufgerufen werden.
|
* Wiedergabe zu stoppen, muss {@link #stop()} aufgerufen werden.
|
||||||
*/
|
*/
|
||||||
void loop();
|
void loop();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stoppt die Wiedergabe. Wird das Medium gerade nicht abgespielt
|
* Stoppt die Wiedergabe. Wird das Medium gerade nicht abgespielt
|
||||||
* {@code isPlaying() == false}, dann passiert nichts.
|
* ({@code isPlaying() == false}), dann passiert nichts.
|
||||||
*/
|
*/
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stoppt die Wiedergabe und gibt alle Resourcen, die für das Medium
|
* Stoppt die Wiedergabe und gibt alle Ressourcen, die für das Medium
|
||||||
* verwendet werden, frei.
|
* verwendet werden, frei.
|
||||||
*/
|
*/
|
||||||
void dispose();
|
void dispose();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fügt dem Medium das angegebene Objekt als {@code AudioListener} hinzu,
|
||||||
|
* der bei Start und Stopp der Wiedergabe informiert wird.
|
||||||
|
*
|
||||||
|
* @param listener Das Listener-Objekt.
|
||||||
|
*/
|
||||||
|
void addAudioListener( AudioListener listener );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt den angegebenen {@code AudioListener} vom Medium.
|
||||||
|
*
|
||||||
|
* @param listener Das Listener-Objekt.
|
||||||
|
*/
|
||||||
|
void removeAudioListener( AudioListener listener );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,33 @@ package schule.ngb.zm.media;
|
|||||||
|
|
||||||
import schule.ngb.zm.util.events.Listener;
|
import schule.ngb.zm.util.events.Listener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface für Klassen, die auf das starten und stoppen der Wiedergabe von
|
||||||
|
* {@link Audio}-Objekten reagieren möchten.
|
||||||
|
* <p>
|
||||||
|
* Implementierende Klassen können sich bei einem Auido-Objekt mittels
|
||||||
|
* {@link Audio#addAudioListener(AudioListener)} anmelden und werden über die
|
||||||
|
* jeweilige Methode informiert, sobald die Wiedergabe gestartet oder gestoppt
|
||||||
|
* wird.
|
||||||
|
*/
|
||||||
public interface AudioListener extends Listener<Audio> {
|
public interface AudioListener extends Listener<Audio> {
|
||||||
|
|
||||||
void start( Audio source );
|
/**
|
||||||
|
* Wird aufgerufen, sobald die Wiedergabe eines Audio-Mediums startet, dem
|
||||||
|
* dieses Objekt mittels {@link Audio#addAudioListener(AudioListener)}
|
||||||
|
* hinzugefügt wurde.
|
||||||
|
*
|
||||||
|
* @param source Das Audio-Medium, dessen Wiedergabe gestartet wurde.
|
||||||
|
*/
|
||||||
|
void playbackStarted( Audio source );
|
||||||
|
|
||||||
void stop( Audio source );
|
/**
|
||||||
|
* Wird aufgerufen, sobald die Wiedergabe eines Audio-Mediums stoppt, dem
|
||||||
|
* dieses Objekt mittels {@link Audio#addAudioListener(AudioListener)}
|
||||||
|
* hinzugefügt wurde.
|
||||||
|
*
|
||||||
|
* @param source Das Audio-Medium, dessen Wiedergabe gestoppt wurde.
|
||||||
|
*/
|
||||||
|
void playbackStopped( Audio source );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package schule.ngb.zm.media;
|
package schule.ngb.zm.media;
|
||||||
|
|
||||||
import schule.ngb.zm.Constants;
|
import schule.ngb.zm.Constants;
|
||||||
|
import schule.ngb.zm.util.events.EventDispatcher;
|
||||||
import schule.ngb.zm.util.tasks.TaskRunner;
|
import schule.ngb.zm.util.tasks.TaskRunner;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,24 +20,14 @@ import java.util.List;
|
|||||||
* Darüber hinaus kann ein Mixer Effekte wie einen
|
* Darüber hinaus kann ein Mixer Effekte wie einen
|
||||||
* {@link #fade(double, int) fadeIn} auf die Medien anwenden.
|
* {@link #fade(double, int) fadeIn} auf die Medien anwenden.
|
||||||
*/
|
*/
|
||||||
public class Mixer implements Audio {
|
@SuppressWarnings( "unused" )
|
||||||
|
public class Mixer implements Audio, AudioListener {
|
||||||
|
|
||||||
private List<AudioWrapper> audios;
|
private List<AudioWrapper> audios;
|
||||||
|
|
||||||
private float volume = 0.8f;
|
private float volume = 0.8f;
|
||||||
|
|
||||||
class AudioWrapper {
|
EventDispatcher<Audio, AudioListener> eventDispatcher;
|
||||||
|
|
||||||
Audio audio;
|
|
||||||
|
|
||||||
float volumeFactor;
|
|
||||||
|
|
||||||
public AudioWrapper( Audio audio, float volumeFactor ) {
|
|
||||||
this.audio = audio;
|
|
||||||
this.volumeFactor = volumeFactor;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Mixer() {
|
public Mixer() {
|
||||||
this.audios = new ArrayList<>(4);
|
this.audios = new ArrayList<>(4);
|
||||||
@@ -45,66 +37,93 @@ public class Mixer implements Audio {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add( Audio pAudio ) {
|
private AudioWrapper findWrapper( Audio pAudio ) {
|
||||||
add(pAudio, 1f);
|
for( AudioWrapper aw: audios ) {
|
||||||
|
if( aw.audio == pAudio ) {
|
||||||
|
return aw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean contains( Audio pAudio ) {
|
||||||
|
return findWrapper(pAudio) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add( Audio pAudio ) {
|
||||||
|
add(pAudio, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fügt ein Audio-Objekt dem Mixer mit dem angegebenen Lautstärke-Faktor
|
||||||
|
* hinzu.
|
||||||
|
* <p>
|
||||||
|
* Der Lautstärke-Faktor setzt die Lautstärke des Audio-Objektes relativ zur
|
||||||
|
* Lautstärke des Mixers. Bei einem Faktor von 1.0 wird die Lautstärke des
|
||||||
|
* Mixers übernommen. Bei einem Wert von 0.5 wird das Objekt halb so laut
|
||||||
|
* abgespielt. Auf diese Weise lässt sich die Lautstärke aller Audio-Objekte
|
||||||
|
* des Mixers gleichzeitig anpassen, während ihre relative Lautstärke
|
||||||
|
* zueinander gleich bleibt.
|
||||||
|
*
|
||||||
|
* @param pAudio Ein Audio-Objekt.
|
||||||
|
* @param pVolumeFactor Der Lautstärke-Faktor.
|
||||||
|
*/
|
||||||
public void add( Audio pAudio, double pVolumeFactor ) {
|
public void add( Audio pAudio, double pVolumeFactor ) {
|
||||||
|
if( !contains(pAudio) ) {
|
||||||
audios.add(new AudioWrapper(pAudio, (float) pVolumeFactor));
|
audios.add(new AudioWrapper(pAudio, (float) pVolumeFactor));
|
||||||
|
} else {
|
||||||
|
findWrapper(pAudio).volumeFactor = (float) pVolumeFactor;
|
||||||
|
}
|
||||||
pAudio.setVolume(pVolumeFactor * volume);
|
pAudio.setVolume(pVolumeFactor * volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt die das angegebene Audio-Objekt aus dem Mixer. Ist das Objekt
|
||||||
|
* nicht Teil des Mixers, passiert nichts.
|
||||||
|
*
|
||||||
|
* @param pAudio Ein Audio-Objekt.
|
||||||
|
*/
|
||||||
public void remove( Audio pAudio ) {
|
public void remove( Audio pAudio ) {
|
||||||
|
Iterator<AudioWrapper> it = audios.listIterator();
|
||||||
|
while( it.hasNext() ) {
|
||||||
|
AudioWrapper aw = it.next();
|
||||||
|
if( aw.audio == pAudio ) {
|
||||||
|
it.remove();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeAll() {
|
public void removeAll() {
|
||||||
audios.clear();
|
audios.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPlaying() {
|
public boolean isPlaying() {
|
||||||
return audios.stream().anyMatch(aw -> aw.audio.isPlaying());
|
return audios.stream().anyMatch(aw -> aw.audio.isPlaying());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLooping() {
|
public boolean isLooping() {
|
||||||
return audios.stream().anyMatch(aw -> aw.audio.isLooping());
|
return audios.stream().anyMatch(aw -> aw.audio.isLooping());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setVolume( double pVolume ) {
|
public void setVolume( double volume ) {
|
||||||
volume = (float) pVolume;
|
this.volume = volume < 0 ? 0f : (float) volume;
|
||||||
audios.stream().forEach(aw -> aw.audio.setVolume(aw.volumeFactor * pVolume));
|
audios.stream().forEach(aw -> aw.audio.setVolume(aw.volumeFactor * volume));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public double getVolume() {
|
public double getVolume() {
|
||||||
return volume;
|
return volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void play() {
|
public void play() {
|
||||||
audios.stream().forEach(aw -> aw.audio.play());
|
audios.stream().forEach(aw -> aw.audio.play());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void playAndWait() {
|
public void playAndWait() {
|
||||||
audios.stream().forEach(aw -> aw.audio.play());
|
audios.stream().forEach(aw -> aw.audio.play());
|
||||||
@@ -117,25 +136,16 @@ public class Mixer implements Audio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void loop() {
|
public void loop() {
|
||||||
audios.stream().forEach(aw -> aw.audio.loop());
|
audios.stream().forEach(aw -> aw.audio.loop());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
audios.stream().forEach(aw -> aw.audio.stop());
|
audios.stream().forEach(aw -> aw.audio.stop());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
if( isPlaying() ) {
|
if( isPlaying() ) {
|
||||||
@@ -178,4 +188,58 @@ public class Mixer implements Audio {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void playbackStarted( Audio source ) {
|
||||||
|
if( eventDispatcher != null ) {
|
||||||
|
eventDispatcher.dispatchEvent("start", Mixer.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void playbackStopped( Audio source ) {
|
||||||
|
if( !isPlaying() ) {
|
||||||
|
if( eventDispatcher != null ) {
|
||||||
|
eventDispatcher.dispatchEvent("stop", Mixer.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addAudioListener( AudioListener listener ) {
|
||||||
|
initializeEventDispatcher().addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAudioListener( AudioListener listener ) {
|
||||||
|
initializeEventDispatcher().removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
|
||||||
|
* aufgerufen, sobald sich auch ein Listener registrieren möchte.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private EventDispatcher<Audio, AudioListener> initializeEventDispatcher() {
|
||||||
|
if( eventDispatcher == null ) {
|
||||||
|
eventDispatcher = new EventDispatcher<>();
|
||||||
|
eventDispatcher.registerEventType("start", (a,l) -> l.playbackStarted(a));
|
||||||
|
eventDispatcher.registerEventType("stop", (a,l) -> l.playbackStopped(a));
|
||||||
|
}
|
||||||
|
return eventDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AudioWrapper {
|
||||||
|
|
||||||
|
Audio audio;
|
||||||
|
|
||||||
|
float volumeFactor;
|
||||||
|
|
||||||
|
public AudioWrapper( Audio audio, float volumeFactor ) {
|
||||||
|
this.audio = audio;
|
||||||
|
this.volumeFactor = volumeFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,83 +1,103 @@
|
|||||||
package schule.ngb.zm.media;
|
package schule.ngb.zm.media;
|
||||||
|
|
||||||
import schule.ngb.zm.util.events.EventDispatcher;
|
|
||||||
import schule.ngb.zm.util.tasks.TaskRunner;
|
|
||||||
import schule.ngb.zm.util.Log;
|
import schule.ngb.zm.util.Log;
|
||||||
import schule.ngb.zm.util.io.ResourceStreamProvider;
|
|
||||||
import schule.ngb.zm.util.Validator;
|
import schule.ngb.zm.util.Validator;
|
||||||
|
import schule.ngb.zm.util.events.EventDispatcher;
|
||||||
|
import schule.ngb.zm.util.io.ResourceStreamProvider;
|
||||||
|
import schule.ngb.zm.util.tasks.TaskRunner;
|
||||||
|
|
||||||
import javax.sound.sampled.*;
|
import javax.sound.sampled.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ein Musikstück, dass im Projekt abgespielt werden soll.
|
* Eine Musik, die abgespielt werden kann.
|
||||||
* <p>
|
* <p>
|
||||||
* Im gegensatz zu einem {@link Sound} sind Musikstücke längere Audiodateien,
|
* Im Gegensatz zu einem {@link Sound} wird {@code Music} für längere
|
||||||
* die zum Beispiel als Hintergrundmusik ablaufen sollen. Die Musik wird daher
|
* Audiodateien benutzt, die zum Beispiel als Hintergrundmusik gespielt werden.
|
||||||
* nicht komplett in den Speicher geladen, sondern direkt aus der Audioquelle
|
* Die Audiodaten werden daher nicht vollständig in den Speicher geladen,
|
||||||
* gestreamt und wiedergegeben.
|
* sondern direkt aus der Quelle gestreamt und direkt wiedergegeben.
|
||||||
|
* <p>
|
||||||
|
* Daher ist es nicht möglich, die länge der Musik im Vorfeld abzufragen oder zu
|
||||||
|
* einer bestimmten Stelle im Stream zu springen.
|
||||||
|
*
|
||||||
|
* <h2>MP3-Dateien verwenden</h2>
|
||||||
|
* Java kann nativ nur Waveform ({@code .wav}) Dateien wiedergeben. Um auch
|
||||||
|
* MP3-Dateien zu nutzen, müssen die Bibliotheken <a href="#">jlayer</a>, <a
|
||||||
|
* href="#">tritonus-share</a> und <a href="#">mp3spi</a> eingebunden werden.
|
||||||
|
* Details zur Verwendung können in der <a
|
||||||
|
* href="https://zeichenmaschine.xyz/installation/#unterstutzung-fur-mp3">Dokumentation
|
||||||
|
* der Zeichenmaschine</a> nachgelesen werden.
|
||||||
*/
|
*/
|
||||||
|
// TODO: Wann sollten Listener beim Loopen informiert werden? Nach jedem Loop oder erst ganz am Ende?
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public class Music implements Audio {
|
public class Music implements Audio {
|
||||||
|
|
||||||
// size of the byte buffer used to read/write the audio stream
|
/**
|
||||||
|
* Größe des verwendeten Input-Puffers für die Audiodaten.
|
||||||
|
*/
|
||||||
private static final int BUFFER_SIZE = 4096;
|
private static final int BUFFER_SIZE = 4096;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ob der Sound gerade abgespielt wird.
|
* Ob der Sound aktuell abgespielt wird.
|
||||||
*/
|
*/
|
||||||
private boolean playing = false;
|
private boolean playing = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ob der Sound gerade in einer Schleife abgespielt wird.
|
* Ob der Sound aktuell in einer Schleife abgespielt wird.
|
||||||
*/
|
*/
|
||||||
private boolean looping = false;
|
private boolean looping = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Quelle des Musikstücks.
|
* Die Quelle der Audiodaten.
|
||||||
*/
|
*/
|
||||||
private String audioSource;
|
private String audioSource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Der AudioStream, um die AUdiosdaten zulsen, falls dieser schon geöffnet
|
* Der {@link AudioInputStream}, um die Audiosdaten zu lesen. {@code null},
|
||||||
* wurde. Sonst {@code null}.
|
* falls noch kein Stream geöffnet wurde.
|
||||||
*/
|
*/
|
||||||
private AudioInputStream audioStream;
|
private AudioInputStream audioStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Line für die Ausgabe, falls diese schon geöffnet wurde. Sonst
|
* Die {@link SourceDataLine} für die Ausgabe. {@code null}, falls die
|
||||||
* {@code null}.
|
* Audiodatei noch nicht geöffnet wurde.
|
||||||
*/
|
*/
|
||||||
private SourceDataLine audioLine;
|
private SourceDataLine audioLine;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Lautstärke der Musik.
|
* Die aktuelle Lautstärke des Mediums.
|
||||||
*/
|
*/
|
||||||
private float volume = 0.8f;
|
private float volume = 0.8f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatcher für Audio-Events (start und stop).
|
||||||
|
*/
|
||||||
EventDispatcher<Audio, AudioListener> eventDispatcher;
|
EventDispatcher<Audio, AudioListener> eventDispatcher;
|
||||||
|
|
||||||
public Music( String source ) {
|
/**
|
||||||
Validator.requireNotNull(source);
|
* Erstellt eine Musik aus der angegebenen Audioquelle.
|
||||||
this.audioSource = source;
|
*
|
||||||
|
* @param audioSource Quelle der Audiodaten.
|
||||||
|
* @throws NullPointerException Falls die Quelle {@code null} ist.
|
||||||
|
* @see ResourceStreamProvider#getResourceURL(String)
|
||||||
|
*/
|
||||||
|
public Music( String audioSource ) {
|
||||||
|
Validator.requireNotNull(audioSource, "audioSource");
|
||||||
|
this.audioSource = audioSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getSource() {
|
public String getSource() {
|
||||||
return audioSource;
|
return audioSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPlaying() {
|
public boolean isPlaying() {
|
||||||
return playing;
|
return playing;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLooping() {
|
public boolean isLooping() {
|
||||||
if( !playing ) {
|
if( !playing ) {
|
||||||
@@ -86,28 +106,21 @@ public class Music implements Audio {
|
|||||||
return looping;
|
return looping;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setVolume( double volume ) {
|
public void setVolume( double volume ) {
|
||||||
this.volume = (float) volume;
|
this.volume = volume < 0 ? 0f : (float) volume;
|
||||||
if( audioLine != null ) {
|
if( audioLine != null ) {
|
||||||
applyVolume();
|
applyVolume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public double getVolume() {
|
public double getVolume() {
|
||||||
return volume;
|
return volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interne Methode, um die gesetzte Lautstärke vor dem Abspielen
|
* Wendet die Lautstärke vor dem Abspielen auf den Audiostream an.
|
||||||
* anzuwenden.
|
|
||||||
*/
|
*/
|
||||||
private void applyVolume() {
|
private void applyVolume() {
|
||||||
FloatControl gainControl =
|
FloatControl gainControl =
|
||||||
@@ -118,24 +131,13 @@ public class Music implements Audio {
|
|||||||
gainControl.setValue(vol);
|
gainControl.setValue(vol);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void play() {
|
public void play() {
|
||||||
if( openLine() ) {
|
if( openLine() ) {
|
||||||
TaskRunner.run(new Runnable() {
|
TaskRunner.run(this::stream);
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
stream();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void playAndWait() {
|
public void playAndWait() {
|
||||||
if( openLine() ) {
|
if( openLine() ) {
|
||||||
@@ -143,18 +145,12 @@ public class Music implements Audio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void loop() {
|
public void loop() {
|
||||||
looping = true;
|
looping = true;
|
||||||
play();
|
play();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
playing = false;
|
playing = false;
|
||||||
@@ -162,9 +158,6 @@ public class Music implements Audio {
|
|||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void dispose() {
|
public synchronized void dispose() {
|
||||||
if( audioLine != null ) {
|
if( audioLine != null ) {
|
||||||
@@ -188,7 +181,17 @@ public class Music implements Audio {
|
|||||||
audioStream = null;
|
audioStream = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Startet den Stream der Audiodaten und damit die Wiedergabe.
|
||||||
|
* <p>
|
||||||
|
* Die {@link #audioLine} muss vorher mit {@link #openLine()} geöffnet
|
||||||
|
* werden, ansonsten passiert nichts.
|
||||||
|
*/
|
||||||
private synchronized void stream() {
|
private synchronized void stream() {
|
||||||
|
if( audioLine == null ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
audioLine.start();
|
audioLine.start();
|
||||||
playing = true;
|
playing = true;
|
||||||
if( eventDispatcher != null ) {
|
if( eventDispatcher != null ) {
|
||||||
@@ -225,6 +228,14 @@ public class Music implements Audio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Öffnet eine {@link SourceDataLine} für die
|
||||||
|
* {@link #audioSource Audioquelle} und bereitet die Wiedergabe vor. Es wird
|
||||||
|
* noch nichts abgespielt.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn die Line geöffnet werden konnte, {@code false}
|
||||||
|
* sonst.
|
||||||
|
*/
|
||||||
private boolean openLine() {
|
private boolean openLine() {
|
||||||
if( audioLine != null ) {
|
if( audioLine != null ) {
|
||||||
return true;
|
return true;
|
||||||
@@ -261,6 +272,15 @@ public class Music implements Audio {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wird aufgerufen, wenn die Wiedergabe beendet wurde. Entweder durch einen
|
||||||
|
* Aufruf von {@link #stop()} oder weil keine Audiodaten mehr vorhanden
|
||||||
|
* sind.
|
||||||
|
* <p>
|
||||||
|
* Nach dem Ende des Streams wird {@link #dispose()} aufgerufen und, falls
|
||||||
|
* das Musikstück in einer Schleife abgespielt wird, der Stream direkt
|
||||||
|
* wieder gestartet.
|
||||||
|
*/
|
||||||
private void streamingStopped() {
|
private void streamingStopped() {
|
||||||
dispose();
|
dispose();
|
||||||
|
|
||||||
@@ -276,24 +296,27 @@ public class Music implements Audio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addListener( AudioListener listener ) {
|
@Override
|
||||||
|
public void addAudioListener( AudioListener listener ) {
|
||||||
initializeEventDispatcher().addListener(listener);
|
initializeEventDispatcher().addListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeListener( AudioListener listener ) {
|
@Override
|
||||||
|
public void removeAudioListener( AudioListener listener ) {
|
||||||
initializeEventDispatcher().removeListener(listener);
|
initializeEventDispatcher().removeListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
|
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
|
||||||
* aufgerufen, soblad sich auch ein Listener registrieren möchte.
|
* aufgerufen, sobald sich der erste Listener anmelden möchte.
|
||||||
* @return
|
*
|
||||||
|
* @return Der {@code EventDispatcher} für dieses Objekt.
|
||||||
*/
|
*/
|
||||||
private EventDispatcher<Audio, AudioListener> initializeEventDispatcher() {
|
private EventDispatcher<Audio, AudioListener> initializeEventDispatcher() {
|
||||||
if( eventDispatcher == null ) {
|
if( eventDispatcher == null ) {
|
||||||
eventDispatcher = new EventDispatcher<>();
|
eventDispatcher = new EventDispatcher<>();
|
||||||
eventDispatcher.registerEventType("start", (a,l) -> l.start(a));
|
eventDispatcher.registerEventType("start", ( a, l ) -> l.playbackStarted(a));
|
||||||
eventDispatcher.registerEventType("stop", (a,l) -> l.stop(a));
|
eventDispatcher.registerEventType("stop", ( a, l ) -> l.playbackStopped(a));
|
||||||
}
|
}
|
||||||
return eventDispatcher;
|
return eventDispatcher;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package schule.ngb.zm.media;
|
|||||||
|
|
||||||
import schule.ngb.zm.util.Log;
|
import schule.ngb.zm.util.Log;
|
||||||
import schule.ngb.zm.util.Validator;
|
import schule.ngb.zm.util.Validator;
|
||||||
|
import schule.ngb.zm.util.events.EventDispatcher;
|
||||||
import schule.ngb.zm.util.io.ResourceStreamProvider;
|
import schule.ngb.zm.util.io.ResourceStreamProvider;
|
||||||
|
|
||||||
import javax.sound.sampled.*;
|
import javax.sound.sampled.*;
|
||||||
@@ -9,78 +10,87 @@ import java.io.IOException;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wiedergabe kurzer Soundclips, die mehrmals wiederverwendet werden.
|
* Ein kurzer Soundclip, der mehrmals wiederverwendet werden kann.
|
||||||
* <p>
|
* <p>
|
||||||
* In Spielen und anderen Projekten gibt es oftmals eine Reihe kurzer Sounds,
|
* In Spielen und anderen Projekten gibt es oftmals eine Reihe kurzer
|
||||||
* die zusammen mit bestimmten Aktionen wiedergegeben werden (zum Beispiel, wenn
|
* Soundclips, die zusammen mit bestimmten Aktionen wiedergegeben werden (zum
|
||||||
* die Spielfigur springt, wenn zwei Objekte kollidieren, usw.). Sounds werden
|
* Beispiel, wenn die Spielfigur springt, wenn zwei Objekte kollidieren, usw.).
|
||||||
* komplett in den Speicher geladen und können dadurch immer wieder, als
|
* Sounds werden vollständig in den Speicher geladen und können immer wieder,
|
||||||
* Schleife oder auch nur Abschnittsweise abgespielt werden.
|
* als Schleife oder auch nur Abschnittsweise, abgespielt werden.
|
||||||
* <p>
|
* <p>
|
||||||
* Für längre Musikstücke (beispielsweise Hintergrundmusik) bietet sich eher die
|
* Für längere Musikstücke (beispielsweise Hintergrundmusik) bietet sich eher
|
||||||
* KLasse {@link Music} an.
|
* die Klasse {@link Music} an.
|
||||||
|
*
|
||||||
|
* <h2>MP3-Dateien verwenden</h2>
|
||||||
|
* Java kann nativ nur Waveform ({@code .wav}) Dateien wiedergeben. Um auch
|
||||||
|
* MP3-Dateien zu nutzen, müssen die Bibliotheken <a href="#">jlayer</a>, <a
|
||||||
|
* href="#">tritonus-share</a> und <a href="#">mp3spi</a> eingebunden werden.
|
||||||
|
* Details zur Verwendung können in der <a
|
||||||
|
* href="https://zeichenmaschine.xyz/installation/#unterstutzung-fur-mp3">Dokumentation
|
||||||
|
* der Zeichenmaschine</a> nachgelesen werden.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings( "unused" )
|
@SuppressWarnings( "unused" )
|
||||||
public class Sound implements Audio {
|
public class Sound implements Audio {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ob der Sound gerade abgespielt wird.
|
* Ob der Sound aktuell abgespielt wird.
|
||||||
*/
|
*/
|
||||||
private boolean playing = false;
|
private boolean playing = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ob der Sound gerade in einer Schleife abgespielt wird.
|
* Ob der Sound aktuell in einer Schleife abgespielt wird.
|
||||||
*/
|
*/
|
||||||
private boolean looping = false;
|
private boolean looping = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Quelle des Musikstücks.
|
* Die Quelle der Audiodaten.
|
||||||
*/
|
*/
|
||||||
private String audioSource;
|
private String audioSource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Der Clip, falls er schon geladen wurde, sonst {@code null}.
|
* Der Clip, falls er schon geladen wurde. Ansonsten {@code null}.
|
||||||
*/
|
*/
|
||||||
private Clip audioClip;
|
private Clip audioClip;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ob die Resourcen des Clips im Speicher nach dem nächsten Abspielen
|
* Ob die Ressourcen des Clips im Speicher nach dem nächsten Abspielen
|
||||||
* freigegeben werden sollen.
|
* freigegeben werden sollen.
|
||||||
*/
|
*/
|
||||||
private boolean disposeAfterPlay = false;
|
private boolean disposeAfterPlay = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Lautstärke des Clips.
|
* Die aktuelle Lautstärke des Clips.
|
||||||
*/
|
*/
|
||||||
private float volume = 0.8f;
|
private float volume = 0.8f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatcher für Audio-Events (start und stop).
|
||||||
|
*/
|
||||||
|
EventDispatcher<Audio, AudioListener> eventDispatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt einen Sound aus der angegebene Quelle.
|
* Erstellt einen Sound aus der angegebene Quelle.
|
||||||
*
|
*
|
||||||
* @param source Ein Dateipfad oder eine Webadresse.
|
* @param source Quelle der Audiodaten.
|
||||||
* @throws NullPointerException Falls die Quelle {@code null} ist.
|
* @throws NullPointerException Falls die Quelle {@code null} ist.
|
||||||
|
* @see ResourceStreamProvider#getResourceURL(String)
|
||||||
*/
|
*/
|
||||||
public Sound( String source ) {
|
public Sound( String source ) {
|
||||||
Validator.requireNotNull(source);
|
Validator.requireNotNull(source, "source");
|
||||||
this.audioSource = source;
|
this.audioSource = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getSource() {
|
public String getSource() {
|
||||||
return audioSource;
|
return audioSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPlaying() {
|
public boolean isPlaying() {
|
||||||
// return audioClip != null && audioClip.isRunning();
|
// return audioClip != null && audioClip.isRunning();
|
||||||
return playing;
|
return playing;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLooping() {
|
public boolean isLooping() {
|
||||||
if( !playing ) {
|
if( !playing ) {
|
||||||
@@ -89,28 +99,21 @@ public class Sound implements Audio {
|
|||||||
return looping;
|
return looping;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setVolume( double volume ) {
|
public void setVolume( double volume ) {
|
||||||
this.volume = (float) volume;
|
this.volume = volume < 0 ? 0f : (float) volume;
|
||||||
if( audioClip != null ) {
|
if( audioClip != null ) {
|
||||||
applyVolume();
|
applyVolume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public double getVolume() {
|
public double getVolume() {
|
||||||
return volume;
|
return volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interne Methode, um die gesetzte Lautstärke vor dem Abspielen
|
* Wendet die Lautstärke vor dem Abspielen auf den Clip an.
|
||||||
* anzuwenden.
|
|
||||||
*/
|
*/
|
||||||
private void applyVolume() {
|
private void applyVolume() {
|
||||||
FloatControl gainControl =
|
FloatControl gainControl =
|
||||||
@@ -121,9 +124,6 @@ public class Sound implements Audio {
|
|||||||
gainControl.setValue(vol);
|
gainControl.setValue(vol);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
looping = false;
|
looping = false;
|
||||||
@@ -133,9 +133,6 @@ public class Sound implements Audio {
|
|||||||
playing = false;
|
playing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void play() {
|
public void play() {
|
||||||
if( this.openClip() ) {
|
if( this.openClip() ) {
|
||||||
@@ -144,9 +141,6 @@ public class Sound implements Audio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void playAndWait() {
|
public void playAndWait() {
|
||||||
this.play();
|
this.play();
|
||||||
@@ -165,7 +159,7 @@ public class Sound implements Audio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spielt den Sound genau einmal ab und gibt danach alle Resourcen des Clips
|
* Spielt den Sound einmal ab und gibt danach alle Ressourcen des Clips
|
||||||
* frei.
|
* frei.
|
||||||
* <p>
|
* <p>
|
||||||
* Der Aufruf ist effektiv gleich zu
|
* Der Aufruf ist effektiv gleich zu
|
||||||
@@ -182,7 +176,7 @@ public class Sound implements Audio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spielt den Sound genau einmal ab und gibt danach alle Resourcen des Clips
|
* Spielt den Sound einmal ab und gibt danach alle Ressourcen des Clips
|
||||||
* frei.
|
* frei.
|
||||||
* <p>
|
* <p>
|
||||||
* Der Aufruf entspricht
|
* Der Aufruf entspricht
|
||||||
@@ -196,17 +190,18 @@ public class Sound implements Audio {
|
|||||||
playAndWait();
|
playAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void loop() {
|
public void loop() {
|
||||||
loop(Clip.LOOP_CONTINUOUSLY);
|
loop(Clip.LOOP_CONTINUOUSLY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wiederholt den Sound die angegebene Anzahl an Wiederholungen ab und
|
* Wiederholt den Sound die angegebene Anzahl an Wiederholungen und stoppt
|
||||||
* stoppt die Wiedergabe dann.
|
* die Wiedergabe dann.
|
||||||
|
* <p>
|
||||||
|
* Wird {@code count} auf {@link Clip#LOOP_CONTINUOUSLY} gesetzt (-1), wird
|
||||||
|
* der Clip unendlich oft wiederholt. Der Aufruf entspricht dann
|
||||||
|
* {@link #loop()}.
|
||||||
*
|
*
|
||||||
* @param count Anzahl der Wiederholungen.
|
* @param count Anzahl der Wiederholungen.
|
||||||
*/
|
*/
|
||||||
@@ -228,20 +223,24 @@ public class Sound implements Audio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void dispose() {
|
public synchronized void dispose() {
|
||||||
if( audioClip != null ) {
|
if( audioClip != null ) {
|
||||||
if( audioClip.isRunning() ) {
|
if( audioClip.isRunning() ) {
|
||||||
audioClip.stop();
|
stop();
|
||||||
}
|
}
|
||||||
audioClip.close();
|
audioClip.close();
|
||||||
audioClip = null;
|
audioClip = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt falls nötig den {@link Clip} für die
|
||||||
|
* {@link #audioSource Audioquelle} und startet die Wiedergabe.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn der Clip geöffnet werden konnte, {@code false}
|
||||||
|
* sonst.
|
||||||
|
*/
|
||||||
private synchronized boolean openClip() {
|
private synchronized boolean openClip() {
|
||||||
if( audioClip != null ) {
|
if( audioClip != null ) {
|
||||||
audioClip.setFramePosition(0);
|
audioClip.setFramePosition(0);
|
||||||
@@ -259,8 +258,16 @@ public class Sound implements Audio {
|
|||||||
audioClip.addLineListener(new LineListener() {
|
audioClip.addLineListener(new LineListener() {
|
||||||
@Override
|
@Override
|
||||||
public void update( LineEvent event ) {
|
public void update( LineEvent event ) {
|
||||||
if( event.getType() == LineEvent.Type.STOP ) {
|
if( event.getType() == LineEvent.Type.START ) {
|
||||||
|
if( eventDispatcher != null ) {
|
||||||
|
eventDispatcher.dispatchEvent("start", Sound.this);
|
||||||
|
}
|
||||||
|
} else if( event.getType() == LineEvent.Type.STOP ) {
|
||||||
playbackStopped();
|
playbackStopped();
|
||||||
|
|
||||||
|
if( eventDispatcher != null ) {
|
||||||
|
eventDispatcher.dispatchEvent("stop", Sound.this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -296,18 +303,47 @@ public class Sound implements Audio {
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interne Methode, die aufgerufen wird, wenn die Wiedergabe gestoppt wird.
|
* Wird aufgerufen, wenn die Wiedergabe beendet wurde. Entweder durch einen
|
||||||
* Entweder durch einen Aufruf von {@link #stop()} oder, weil die Wiedergabe
|
* Aufruf von {@link #stop()} oder, weil die Wiedergabe nach
|
||||||
* nach {@link #playOnce()} beendet wurde.
|
* {@link #playOnce()} beendet wurde.
|
||||||
|
* <p>
|
||||||
|
* Falls {@link #disposeAfterPlay} gesetzt ist, wird nach dem Ende der
|
||||||
|
* Wiedergabe {@link #dispose()} aufgerufen.
|
||||||
*/
|
*/
|
||||||
private void playbackStopped() {
|
private void playbackStopped() {
|
||||||
playing = false;
|
playing = false;
|
||||||
|
|
||||||
if( disposeAfterPlay ) {
|
if( disposeAfterPlay ) {
|
||||||
this.dispose();
|
this.dispose();
|
||||||
disposeAfterPlay = false;
|
disposeAfterPlay = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addAudioListener( AudioListener listener ) {
|
||||||
|
initializeEventDispatcher().addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAudioListener( AudioListener listener ) {
|
||||||
|
initializeEventDispatcher().removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
|
||||||
|
* aufgerufen, sobald sich der erste Listener anmelden möchte.
|
||||||
|
*
|
||||||
|
* @return Der {@code EventDispatcher} für dieses Objekt.
|
||||||
|
*/
|
||||||
|
private EventDispatcher<Audio, AudioListener> initializeEventDispatcher() {
|
||||||
|
if( eventDispatcher == null ) {
|
||||||
|
eventDispatcher = new EventDispatcher<>();
|
||||||
|
eventDispatcher.registerEventType("start", ( a, l ) -> l.playbackStarted(a));
|
||||||
|
eventDispatcher.registerEventType("stop", ( a, l ) -> l.playbackStopped(a));
|
||||||
|
}
|
||||||
|
return eventDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
private static final Log LOG = Log.getLogger(Sound.class);
|
private static final Log LOG = Log.getLogger(Sound.class);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/main/java/schule/ngb/zm/media/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Klassen zur Einbettung von Mediendateien.
|
||||||
|
* <p>
|
||||||
|
* Mit Medien sind vor allem Audio und Videodateien gemeint. Aktuell kann die
|
||||||
|
* Zeichenmaschine Audiodateien verwenden.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.media;
|
||||||
@@ -93,32 +93,20 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
rows * columns);
|
rows * columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public int columns() {
|
public int columns() {
|
||||||
return columns;
|
return columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public int rows() {
|
public int rows() {
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
int idx( int r, int c ) {
|
int idx( int r, int c ) {
|
||||||
return c * rows + r;
|
return c * rows + r;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public double get( int row, int col ) {
|
public double get( int row, int col ) {
|
||||||
try {
|
try {
|
||||||
@@ -128,9 +116,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix set( int row, int col, double value ) {
|
public MLMatrix set( int row, int col, double value ) {
|
||||||
try {
|
try {
|
||||||
@@ -141,52 +126,34 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix initializeRandom() {
|
public MLMatrix initializeRandom() {
|
||||||
return initializeRandom(-1.0, 1.0);
|
return initializeRandom(-1.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix initializeRandom( double lower, double upper ) {
|
public MLMatrix initializeRandom( double lower, double upper ) {
|
||||||
applyInPlace(( d ) -> ((upper - lower) * Constants.random()) + lower);
|
applyInPlace(( d ) -> ((upper - lower) * Constants.random()) + lower);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix initializeOne() {
|
public MLMatrix initializeOne() {
|
||||||
applyInPlace(( d ) -> 1.0);
|
applyInPlace(( d ) -> 1.0);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix initializeZero() {
|
public MLMatrix initializeZero() {
|
||||||
applyInPlace(( d ) -> 0.0);
|
applyInPlace(( d ) -> 0.0);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix duplicate() {
|
public MLMatrix duplicate() {
|
||||||
return new DoubleMatrix(this);
|
return new DoubleMatrix(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix multiplyTransposed( MLMatrix B ) {
|
public MLMatrix multiplyTransposed( MLMatrix B ) {
|
||||||
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
||||||
@@ -208,9 +175,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix multiplyAddBias( final MLMatrix B, final MLMatrix C ) {
|
public MLMatrix multiplyAddBias( final MLMatrix B, final MLMatrix C ) {
|
||||||
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
||||||
@@ -233,9 +197,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix transposedMultiplyAndScale( final MLMatrix B, final double scalar ) {
|
public MLMatrix transposedMultiplyAndScale( final MLMatrix B, final double scalar ) {
|
||||||
/*return new DoubleMatrix(IntStream.range(0, columns).parallel().mapToObj(
|
/*return new DoubleMatrix(IntStream.range(0, columns).parallel().mapToObj(
|
||||||
@@ -258,9 +219,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix add( MLMatrix B ) {
|
public MLMatrix add( MLMatrix B ) {
|
||||||
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
||||||
@@ -277,9 +235,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix addInPlace( MLMatrix B ) {
|
public MLMatrix addInPlace( MLMatrix B ) {
|
||||||
for( int j = 0; j < columns; j++ ) {
|
for( int j = 0; j < columns; j++ ) {
|
||||||
@@ -290,9 +245,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix sub( MLMatrix B ) {
|
public MLMatrix sub( MLMatrix B ) {
|
||||||
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
||||||
@@ -309,9 +261,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return diff;
|
return diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix colSums() {
|
public MLMatrix colSums() {
|
||||||
/*DoubleMatrix colSums = new DoubleMatrix(1, columns);
|
/*DoubleMatrix colSums = new DoubleMatrix(1, columns);
|
||||||
@@ -331,9 +280,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return colSums;
|
return colSums;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix scaleInPlace( final double scalar ) {
|
public MLMatrix scaleInPlace( final double scalar ) {
|
||||||
for( int i = 0; i < coefficients.length; i++ ) {
|
for( int i = 0; i < coefficients.length; i++ ) {
|
||||||
@@ -342,9 +288,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix scaleInPlace( final MLMatrix S ) {
|
public MLMatrix scaleInPlace( final MLMatrix S ) {
|
||||||
for( int j = 0; j < columns; j++ ) {
|
for( int j = 0; j < columns; j++ ) {
|
||||||
@@ -355,9 +298,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix apply( DoubleUnaryOperator op ) {
|
public MLMatrix apply( DoubleUnaryOperator op ) {
|
||||||
DoubleMatrix result = new DoubleMatrix(rows, columns);
|
DoubleMatrix result = new DoubleMatrix(rows, columns);
|
||||||
@@ -367,9 +307,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix applyInPlace( DoubleUnaryOperator op ) {
|
public MLMatrix applyInPlace( DoubleUnaryOperator op ) {
|
||||||
for( int i = 0; i < coefficients.length; i++ ) {
|
for( int i = 0; i < coefficients.length; i++ ) {
|
||||||
|
|||||||
16
src/main/java/schule/ngb/zm/ml/package-info.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paekt enthält Klassen für Experimente mit Verfahren des maschinellen
|
||||||
|
* Lernens (ML).
|
||||||
|
* <p>
|
||||||
|
* Die hier implementierten Klassen sind eine prototypische Umsetzung von
|
||||||
|
* einfachen neuronalen Netzwerken, mit denen an kleinen Problemstellungen
|
||||||
|
* experimentell Modelle trainiert und angewandt werden können.
|
||||||
|
* <p>
|
||||||
|
* Die Implementierungen sind nicht optimiert und setzen auf native
|
||||||
|
* Java-Methoden. Daher sind sie nur für die Anwendung auf extrem kleine Modelle
|
||||||
|
* in Bildungskontexten gedacht.
|
||||||
|
* <p>
|
||||||
|
* Durch Einbettung wissenschaftlicher Bibliotheken mit optimierten Operationen
|
||||||
|
* lassen sich bessere Ergebnisse erreichen.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.ml;
|
||||||
13
src/main/java/schule/ngb/zm/package-info.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* <h2>Die Zeichenmaschine</h2>
|
||||||
|
* <p>
|
||||||
|
* Die <b>Zeichenmaschine</b> ist eine für den Informatikunterricht entwickelte
|
||||||
|
* Bibliothek, die unter anderem an <a
|
||||||
|
* href="https://processing.org/">Processing</a> angelehnt ist. Die Bibliothek
|
||||||
|
* soll einige der üblichen Anfängerschwierigkeiten mit Java vereinfachen und
|
||||||
|
* für Schülerinnen und Schüler im Unterricht nutzbar machen.
|
||||||
|
* <p>
|
||||||
|
* Eine umfassende Dokumentation ist unter <a
|
||||||
|
* href="https://zeichenmaschine.xyz">zeichenmaschine.xyz</a> verfügbar.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm;
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
package schule.ngb.zm.shapes;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
|
||||||
|
|
||||||
import java.awt.GradientPaint;
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.Paint;
|
|
||||||
import java.awt.RadialGradientPaint;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basisklasse für Formen, die eine Füllung besitzen können.
|
|
||||||
* <p>
|
|
||||||
* Formen mit einer Füllung können auch immer eine Konturlinie besitzen.
|
|
||||||
*/
|
|
||||||
public abstract class FilledShape extends StrokedShape {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die aktuelle Füllfarbe der Form oder {@code null}, wenn die Form nicht
|
|
||||||
* gefüllt werden soll.
|
|
||||||
*/
|
|
||||||
protected Color fillColor = DEFAULT_FILLCOLOR;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Der aktuelle Farbverlauf der Form oder {@code null}, wenn die Form keinen
|
|
||||||
* Farbverlauf besitzt.
|
|
||||||
*/
|
|
||||||
protected Paint fill = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt die aktuelle Füllfarbe der Form zurück.
|
|
||||||
*
|
|
||||||
* @return Die aktuelle Füllfarbe oder {@code null}.
|
|
||||||
*/
|
|
||||||
public Color getFillColor() {
|
|
||||||
return fillColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllfarbe auf die angegebene Farbe.
|
|
||||||
*
|
|
||||||
* @param color Die neue Füllfarbe oder {@code null}.
|
|
||||||
* @see Color
|
|
||||||
*/
|
|
||||||
public void setFillColor( Color color ) {
|
|
||||||
fillColor = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllfarbe auf die angegebene Farbe und setzt die Transparenz
|
|
||||||
* auf den angegebenen Wert. 0 is komplett durchsichtig und 255 komplett
|
|
||||||
* deckend.
|
|
||||||
*
|
|
||||||
* @param color Die neue Füllfarbe oder {@code null}.
|
|
||||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
|
||||||
* @see Color#Color(Color, int)
|
|
||||||
*/
|
|
||||||
public void setFillColor( Color color, int alpha ) {
|
|
||||||
setFillColor(new Color(color, alpha));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllfarbe auf einen Grauwert mit der angegebenen Intensität. 0
|
|
||||||
* entspricht schwarz, 255 entspricht weiß.
|
|
||||||
*
|
|
||||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
|
||||||
* @see Color#Color(int)
|
|
||||||
*/
|
|
||||||
public void setFillColor( int gray ) {
|
|
||||||
setFillColor(gray, gray, gray, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllfarbe auf einen Grauwert mit der angegebenen Intensität und
|
|
||||||
* dem angegebenen Transparenzwert. Der Grauwert 0 entspricht schwarz, 255
|
|
||||||
* entspricht weiß.
|
|
||||||
*
|
|
||||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
|
||||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
|
||||||
* @see Color#Color(int, int)
|
|
||||||
*/
|
|
||||||
public void setFillColor( int gray, int alpha ) {
|
|
||||||
setFillColor(gray, gray, gray, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllfarbe auf die Farbe mit den angegebenen Rot-, Grün- und
|
|
||||||
* Blauanteilen.
|
|
||||||
*
|
|
||||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @see Color#Color(int, int, int)
|
|
||||||
* @see <a
|
|
||||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
|
||||||
*/
|
|
||||||
public void setFillColor( int red, int green, int blue ) {
|
|
||||||
setFillColor(red, green, blue, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllfarbe auf die Farbe mit den angegebenen Rot-, Grün- und
|
|
||||||
* Blauanteilen und dem angegebenen Transparenzwert.
|
|
||||||
*
|
|
||||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param alpha Ein Transparenzwert zwischen 0 und 25
|
|
||||||
* @see Color#Color(int, int, int, int)
|
|
||||||
* @see <a
|
|
||||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
|
||||||
*/
|
|
||||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
|
||||||
setFillColor(new Color(red, green, blue, alpha));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Entfernt die Füllung der Form.
|
|
||||||
*/
|
|
||||||
public void noFill() {
|
|
||||||
setFillColor(null);
|
|
||||||
noGradient();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllfarbe auf den Standardwert zurück.
|
|
||||||
*
|
|
||||||
* @see schule.ngb.zm.Constants#DEFAULT_FILLCOLOR
|
|
||||||
*/
|
|
||||||
public void resetFill() {
|
|
||||||
setFillColor(DEFAULT_FILLCOLOR);
|
|
||||||
noGradient();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllung auf einen linearen Farbverlauf, der am Punkt
|
|
||||||
* ({@code fromX}, {@code fromY}) mit der Farbe {@code from} startet und am
|
|
||||||
* Punkt (({@code toX}, {@code toY}) mit der Farbe {@code to} endet.
|
|
||||||
*
|
|
||||||
* @param fromX x-Koordinate des Startpunktes.
|
|
||||||
* @param fromY y-Koordinate des Startpunktes.
|
|
||||||
* @param from Farbe am Startpunkt.
|
|
||||||
* @param toX x-Koordinate des Endpunktes.
|
|
||||||
* @param toY y-Koordinate des Endpunktes.
|
|
||||||
* @param to Farbe am Endpunkt.
|
|
||||||
*/
|
|
||||||
public void setGradient( double fromX, double fromY, Color from, double toX, double toY, Color to ) {
|
|
||||||
setFillColor(from);
|
|
||||||
fill = new GradientPaint(
|
|
||||||
(float) fromX, (float) fromY, from.getJavaColor(),
|
|
||||||
(float) toX, (float) toY, to.getJavaColor()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllung auf einen kreisförmigen (radialen) Farbverlauf, mit dem
|
|
||||||
* Zentrum im Punkt ({@code centerX}, {@code centerY}) und dem angegebenen
|
|
||||||
* Radius. Der Verlauf starte im Zentrum mit der Farbe {@code from} und
|
|
||||||
* endet am Rand des durch den Radius beschriebenen Kreises mit der Farbe
|
|
||||||
* {@code to}.
|
|
||||||
*
|
|
||||||
* @param centerX x-Koordinate des Kreismittelpunktes.
|
|
||||||
* @param centerY y-Koordinate des Kreismittelpunktes.
|
|
||||||
* @param radius Radius des Kreises.
|
|
||||||
* @param from Farbe im Zentrum des Kreises.
|
|
||||||
* @param to Farbe am Rand des Kreises.
|
|
||||||
*/
|
|
||||||
public void setGradient( double centerX, double centerY, double radius, Color from, Color to ) {
|
|
||||||
setFillColor(from);
|
|
||||||
fill = new RadialGradientPaint(
|
|
||||||
(float) centerX, (float) centerY, (float) radius,
|
|
||||||
new float[]{0f, 1f},
|
|
||||||
new java.awt.Color[]{from.getJavaColor(), to.getJavaColor()});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Entfernt den Farbverlauf von der Form.
|
|
||||||
*/
|
|
||||||
public void noGradient() {
|
|
||||||
fill = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hilfsmethode für Unterklassen, um die angegebene Form mit der aktuellen
|
|
||||||
* Füllung auf den Grafik-Kontext zu zeichnen. Die Methode verändert
|
|
||||||
* gegebenenfalls die aktuelle Farbe des Grafikobjekts und setzt sie nicht
|
|
||||||
* auf den Ursprungswert zurück, wie von {@link #draw(Graphics2D)}
|
|
||||||
* gefordert. Dies sollte die aufrufende Unterklasse übernehmen.
|
|
||||||
*
|
|
||||||
* @param shape Die zu zeichnende Java-AWT Form
|
|
||||||
* @param graphics Das Grafikobjekt.
|
|
||||||
*/
|
|
||||||
protected void fillShape( java.awt.Shape shape, Graphics2D graphics ) {
|
|
||||||
if( fill != null ) {
|
|
||||||
graphics.setPaint(fill);
|
|
||||||
graphics.fill(shape);
|
|
||||||
} else if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
|
||||||
graphics.setColor(fillColor.getJavaColor());
|
|
||||||
graphics.fill(shape);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,25 @@
|
|||||||
package schule.ngb.zm.shapes;
|
package schule.ngb.zm.shapes;
|
||||||
|
|
||||||
|
import schule.ngb.zm.BasicDrawable;
|
||||||
|
import schule.ngb.zm.Fillable;
|
||||||
import schule.ngb.zm.Options;
|
import schule.ngb.zm.Options;
|
||||||
|
import schule.ngb.zm.Strokeable;
|
||||||
|
import schule.ngb.zm.util.Validator;
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basisklasse für alle Formen in der Zeichenmaschine.
|
* Dies ist die Basisklasse für alle Formen in der Zeichenmaschine.
|
||||||
* <p>
|
* <p>
|
||||||
* Alle Formen sind als Unterklassen von {@code Shape} implementiert.
|
* Alle Formen sind als Unterklassen von {@code Shape} implementiert.
|
||||||
* <p>
|
* <p>
|
||||||
* Neben den abstrakten Methoden implementieren Unterklassen mindestens zwei
|
* Neben den abstrakten Methoden implementieren Unterklassen mindestens zwei
|
||||||
* Konstruktoren. Einen Konstruktor, der die Form mit vom Nutzer gegebenen
|
* Konstruktoren. Einen Konstruktor, der die Form mit vom Nutzer gegebenen
|
||||||
* Parametern initialisiert und einen, der die Werten einer anderen Form
|
* Parametern initialisiert und einen, der die Werte einer anderen Form
|
||||||
* desselben Typs übernimmt. In der Klasse {@link Circle} sind die Konstruktoren
|
* desselben Typs übernimmt. In der Klasse {@link Circle} sind die Konstruktoren
|
||||||
* beispielsweise so implementiert:
|
* beispielsweise so implementiert:
|
||||||
*
|
|
||||||
* <pre><code>
|
* <pre><code>
|
||||||
* public Circle( double x, double y, double radius ) {
|
* public Circle( double x, double y, double radius ) {
|
||||||
* super(x, y);
|
* super(x, y);
|
||||||
@@ -34,50 +36,45 @@ import java.awt.geom.Point2D;
|
|||||||
* eine {@link #equals(Object)} Methode.
|
* eine {@link #equals(Object)} Methode.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings( "unused" )
|
@SuppressWarnings( "unused" )
|
||||||
public abstract class Shape extends FilledShape {
|
public abstract class Shape extends BasicDrawable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* x-Koordinate der Form.
|
* Speichert die x-Koordinate der Form.
|
||||||
*/
|
*/
|
||||||
protected double x;
|
protected double x;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* y-Koordinate der Form.
|
* Speichert die y-Koordinate der Form.
|
||||||
*/
|
*/
|
||||||
protected double y;
|
protected double y;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rotation in Grad um den Punkt (x, y).
|
* Speichert die Rotation in Grad um den Punkt (x, y).
|
||||||
*/
|
*/
|
||||||
protected double rotation = 0.0;
|
protected double rotation = 0.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skalierungsfaktor.
|
* Speichert den Skalierungsfaktor.
|
||||||
*/
|
*/
|
||||||
protected double scale = 1.0;
|
protected double scale = 1.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ob die Form angezeigt werden soll.
|
* Speichert den Ankerpunkt.
|
||||||
*/
|
|
||||||
protected boolean visible = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ankerpunkt der Form.
|
|
||||||
*/
|
*/
|
||||||
protected Options.Direction anchor = Options.Direction.CENTER;
|
protected Options.Direction anchor = Options.Direction.CENTER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt die x- und y-Koordinate der Form auf 0.
|
* Erstellt eine neue Form mit den Koordinaten {@code (0,0)}.
|
||||||
*/
|
*/
|
||||||
public Shape() {
|
public Shape() {
|
||||||
this(0.0, 0.0);
|
this(0.0, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt die x- und y-Koordinate der Form.
|
* Erstellt eine Form mit den angegebenen Koordinaten.
|
||||||
*
|
*
|
||||||
* @param x
|
* @param x Die x-Koordinate.
|
||||||
* @param y
|
* @param y Die y-Koordinate.
|
||||||
*/
|
*/
|
||||||
public Shape( double x, double y ) {
|
public Shape( double x, double y ) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
@@ -85,39 +82,7 @@ public abstract class Shape extends FilledShape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ob die Form angezeigt wird oder nicht.
|
* Liefert die aktuelle x-Koordinate der Form.
|
||||||
*
|
|
||||||
* @return {@code true}, wenn die From angezeigt werden soll, {@code false}
|
|
||||||
* sonst.
|
|
||||||
*/
|
|
||||||
public boolean isVisible() {
|
|
||||||
return visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Versteckt die Form.
|
|
||||||
*/
|
|
||||||
public void hide() {
|
|
||||||
visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zeigt die Form an.
|
|
||||||
*/
|
|
||||||
public void show() {
|
|
||||||
visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Versteckt die Form, wenn sie derzeit angezeigt wird und zeigt sie
|
|
||||||
* andernfalls an.
|
|
||||||
*/
|
|
||||||
public void toggle() {
|
|
||||||
visible = !visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt die x-Koordinate der Form zurück.
|
|
||||||
*
|
*
|
||||||
* @return Die x-Koordinate.
|
* @return Die x-Koordinate.
|
||||||
*/
|
*/
|
||||||
@@ -135,7 +100,7 @@ public abstract class Shape extends FilledShape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die y-Koordinate der Form zurück.
|
* Liefert die aktuelle y-Koordinate der Form.
|
||||||
*
|
*
|
||||||
* @return Die y-Koordinate.
|
* @return Die y-Koordinate.
|
||||||
*/
|
*/
|
||||||
@@ -144,44 +109,44 @@ public abstract class Shape extends FilledShape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt die y-Koordinate der Form.
|
* Setzt die x-Koordinate der Form.
|
||||||
*
|
*
|
||||||
* @param y Die neue y-Koordinate.
|
* @param y Die neue y-Koordinate der Form.
|
||||||
*/
|
*/
|
||||||
public void setY( double y ) {
|
public void setY( double y ) {
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die Breite dieser Form zurück.
|
* Liefert die aktuelle Breite dieser Form.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Breite einer Form ist immer die Breite ihrer Begrenzung,
|
* Die Breite einer Form ist immer die Breite ihrer Begrenzung,
|
||||||
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
||||||
* angewandt wurden.
|
* angewandt wurden.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit
|
* Die Begrenzungen der tatsächlich gezeichneten Form wird mit
|
||||||
* {@link #getBounds()} abgerufen werden.
|
* {@link #getBounds()} abgerufen.
|
||||||
*
|
*
|
||||||
* @return
|
* @return Die Breite der Form.
|
||||||
*/
|
*/
|
||||||
public abstract double getWidth();
|
public abstract double getWidth();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die Höhe dieser Form zurück.
|
* Liefert die aktuelle Höhe dieser Form.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Höhe einer Form ist immer die Höhe ihrer Begrenzung,
|
* Die Höhe einer Form ist immer die Höhe ihrer Begrenzung,
|
||||||
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
||||||
* angewandt wurden.
|
* angewandt wurden.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit
|
* Die Begrenzungen der tatsächlich gezeichneten Form wird mit
|
||||||
* {@link #getBounds()} abgerufen werden.
|
* {@link #getBounds()} abgerufen.
|
||||||
*
|
*
|
||||||
* @return
|
* @return Die Höhe der Form.
|
||||||
*/
|
*/
|
||||||
public abstract double getHeight();
|
public abstract double getHeight();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die Rotation in Grad zurück.
|
* Liefert die Rotation in Grad.
|
||||||
*
|
*
|
||||||
* @return Rotation in Grad.
|
* @return Rotation in Grad.
|
||||||
*/
|
*/
|
||||||
@@ -191,20 +156,32 @@ public abstract class Shape extends FilledShape {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Dreht die Form um den angegebenen Winkel um ihren Ankerpunkt.
|
* Dreht die Form um den angegebenen Winkel um ihren Ankerpunkt.
|
||||||
|
*
|
||||||
* @param angle Drehwinkel in Grad.
|
* @param angle Drehwinkel in Grad.
|
||||||
*/
|
*/
|
||||||
public void rotate( double angle ) {
|
public void rotate( double angle ) {
|
||||||
this.rotation += angle % 360;
|
this.rotation += angle % 360;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void rotateTo( double angle ) {
|
/**
|
||||||
this.rotation = angle % 360;
|
* Dreht die Form um den angegebenen Winkel um das angegebene Drehzentrum.
|
||||||
}
|
*
|
||||||
|
* @param center Das Drehzentrum der Drehung.
|
||||||
|
* @param angle Der Drehwinkel.
|
||||||
|
*/
|
||||||
public void rotate( Point2D center, double angle ) {
|
public void rotate( Point2D center, double angle ) {
|
||||||
|
Validator.requireNotNull(center, "center");
|
||||||
rotate(center.getX(), center.getY(), angle);
|
rotate(center.getX(), center.getY(), angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dreht die Form um den angegebenen Drehwinkel um die angegbenen
|
||||||
|
* Koordinaten als Drehzentrum.
|
||||||
|
*
|
||||||
|
* @param x x-Koordiante des Drehzentrums.
|
||||||
|
* @param y y-Koordiante des Drehzentrums.
|
||||||
|
* @param angle Drehwinkel in Grad.
|
||||||
|
*/
|
||||||
public void rotate( double x, double y, double angle ) {
|
public void rotate( double x, double y, double angle ) {
|
||||||
this.rotation += angle % 360;
|
this.rotation += angle % 360;
|
||||||
|
|
||||||
@@ -219,6 +196,15 @@ public abstract class Shape extends FilledShape {
|
|||||||
this.y = y2 + y;
|
this.y = y2 + y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Drehung der Form auf den angegebenen Winkel.
|
||||||
|
*
|
||||||
|
* @param angle Drehwinkel in Grad.
|
||||||
|
*/
|
||||||
|
public void rotateTo( double angle ) {
|
||||||
|
this.rotation = angle % 360;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt den aktuellen Skalierungsfaktor zurück.
|
* Gibt den aktuellen Skalierungsfaktor zurück.
|
||||||
*
|
*
|
||||||
@@ -227,96 +213,161 @@ public abstract class Shape extends FilledShape {
|
|||||||
public double getScale() {
|
public double getScale() {
|
||||||
return scale;
|
return scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt den Skalierungsfaktor auf den angegebenen Faktor.
|
||||||
|
* <p>
|
||||||
|
* Bei einem Faktor größer 0 wird die Form vergrößert, bei einem Faktor
|
||||||
|
* kleiner 0 verkleinert. Bei negativen Werten wird die Form entlang der x-
|
||||||
|
* bzw. y-Achse gespiegelt.
|
||||||
|
* <p>
|
||||||
|
* Das Seitenverhältnis wird immer beibehalten.
|
||||||
|
*
|
||||||
|
* @param factor Der Skalierungsfaktor.
|
||||||
|
*/
|
||||||
public void scale( double factor ) {
|
public void scale( double factor ) {
|
||||||
scale = factor;
|
scale = factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skaliert die Form um den angegebenen Faktor.
|
||||||
|
* <p>
|
||||||
|
* Bei einem Faktor größer 0 wird die Form vergrößert, bei einem Faktor
|
||||||
|
* kleiner 0 verkleinert. Bei negativen Werten wird die Form entlang der x-
|
||||||
|
* bzw. y-Achse gespiegelt.
|
||||||
|
* <p>
|
||||||
|
* Die Skalierung wird zusätzlich zur aktuellen Skalierung angewandt. Wurde
|
||||||
|
* die Form zuvor um den Faktor 0.5 verkleinert und wird dann um 1.5
|
||||||
|
* vergrößert, dann ist die Form im Anschluss ein Drittel kleiner als zu
|
||||||
|
* Beginn ({@code 0.5 * 1.5 = 0.75}).
|
||||||
|
*
|
||||||
|
* @param factor Der Skalierungsfaktor.
|
||||||
|
*/
|
||||||
public void scaleBy( double factor ) {
|
public void scaleBy( double factor ) {
|
||||||
scale(scale * factor);
|
scale(scale * factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to, Options.Direction dir ) {
|
/**
|
||||||
Point2D apDir = getAbsAnchorPoint(dir);
|
* Liefert den aktuellen Ankerpunkt der Form.
|
||||||
Point2D apInv = getAbsAnchorPoint(dir.inverse());
|
*
|
||||||
setGradient(apInv.getX(), apInv.getY(), from, apDir.getX(), apDir.getY(), to);
|
* @return Der Ankerpunkt.
|
||||||
}
|
*/
|
||||||
|
|
||||||
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to ) {
|
|
||||||
Point2D ap = getAbsAnchorPoint(CENTER);
|
|
||||||
setGradient(ap.getX(), ap.getY(), Math.min(ap.getX(), ap.getY()), from, to);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Options.Direction getAnchor() {
|
public Options.Direction getAnchor() {
|
||||||
return anchor;
|
return anchor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt den Ankerpunkt der Form basierend auf der angegebenen
|
* Setzt den Ankerpunkt der Form auf die angegebene Richtung.
|
||||||
* {@link Options.Direction Richtung}.
|
|
||||||
* <p>
|
* <p>
|
||||||
* Für das Setzen des Ankers muss das
|
* Jede Form hat einen Ankerpunkt, von dem aus sie gezeichnet wird. Jede
|
||||||
* {@link #getBounds() begrenzende Rechteck} berechnet werden. Unterklassen
|
* {@link schule.ngb.zm.Options.Direction Richtung} beschreibt einen der
|
||||||
* sollten die Methode überschreiben, wenn der Anker auch direkt gesetzt
|
* Neun Ankerpunkte:
|
||||||
* werden kann.
|
* <pre>
|
||||||
|
* NW────N────NE
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* W C E
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* SW────S────SE
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* Für den Ankerpunkt {@link #CENTER} wird die Form also ausgehend von den
|
||||||
|
* Koordinaten {@link #x} und {@link #y} um die Hälfte der Breite nach links
|
||||||
|
* und rechts, sowie um die Hälfte der Höhe nach oben und unten gezeichnet.
|
||||||
|
* Fpr den Ankerpunkt {@link #NORTHWEST} dagegen um die gesamte Breite nach
|
||||||
|
* rechts und die Höhe nach unten.
|
||||||
|
* <pre>
|
||||||
|
* setAnchor(CENTER) │ setAnchor(NORTHWEST)
|
||||||
|
* ┌───────────┐ │
|
||||||
|
* │ │ │
|
||||||
|
* │ │ │
|
||||||
|
* │ (x,y) │ │ (x,y)─────────┐
|
||||||
|
* │ │ │ │ │
|
||||||
|
* │ │ │ │ │
|
||||||
|
* └───────────┘ │ │ │
|
||||||
|
* │ │ │
|
||||||
|
* │ │ │
|
||||||
|
* │ └───────────┘
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* Der Ankerpunkt der Form bestimmt bei Transformationen auch die Position
|
||||||
|
* des Drehzentrums und anderer relativer Koordinaten bezüglich der Form.
|
||||||
*
|
*
|
||||||
* @param anchor
|
* @param anchor Der Ankerpunkt.
|
||||||
*/
|
*/
|
||||||
public void setAnchor( Options.Direction anchor ) {
|
public void setAnchor( Options.Direction anchor ) {
|
||||||
if( anchor != null ) {
|
Validator.requireNotNull(anchor, "anchor");
|
||||||
this.anchor = anchor;
|
this.anchor = anchor;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bestimmt die relativen Koordinaten des angegebenen Ankerpunkt basierend
|
* Bestimmt die relativen Koordinaten des angegebenen Ankerpunkts basierend
|
||||||
* auf der angegebenen Breite und Höhe des umschließenden Rechtecks.
|
* auf der angegebenen Breite und Höhe des umschließenden Rechtecks.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Koordinaten des Ankerpunkt werden relativ zur oberen linken Ecke des
|
* Die Koordinaten des Ankerpunktes werden relativ zur oberen linken Ecke
|
||||||
* Rechtecks mit der Breite {@code width} und der Höhe {@code height}
|
* des Rechtecks mit der Breite {@code width} und der Höhe {@code height}
|
||||||
* bestimmt.
|
* bestimmt. Der Ankerpunkt {@link #NORTHWEST} hat daher immer das Ergebnis
|
||||||
|
* {@code (0,0)} und {@link #SOUTHEAST} {@code (width, height)}.
|
||||||
|
* <pre>
|
||||||
|
* (0,0)───(w/2,0)───(w,0)
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* (0,h/2) (w/2,h/2) (w,h/2)
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* (0,h)───(w/2,h)───(w,h)
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param width Breite des umschließdenden Rechtecks.
|
* @param width Breite des umschließenden Rechtecks.
|
||||||
* @param height Höhe des umschließdenden Rechtecks.
|
* @param height Höhe des umschließenden Rechtecks.
|
||||||
* @param anchor Gesuchter Ankerpunkt.
|
* @param anchor Gesuchter Ankerpunkt.
|
||||||
* @return Ein {@link Point2D} mit den relativen Koordinaten.
|
* @return Ein {@link Point2D} mit den relativen Koordinaten.
|
||||||
*/
|
*/
|
||||||
protected static Point2D.Double getAnchorPoint( double width, double height, Options.Direction anchor ) {
|
public static Point2D.Double getAnchorPoint( double width, double height, Options.Direction anchor ) {
|
||||||
double wHalf = width * .5, hHalf = height * .5;
|
double wHalf = width * .5, hHalf = height * .5;
|
||||||
|
|
||||||
// anchor == CENTER
|
return new Point2D.Double(
|
||||||
Point2D.Double anchorPoint = new Point2D.Double(
|
|
||||||
wHalf + wHalf * anchor.x,
|
wHalf + wHalf * anchor.x,
|
||||||
hHalf + hHalf * anchor.y
|
hHalf + hHalf * anchor.y
|
||||||
);
|
);
|
||||||
|
|
||||||
return anchorPoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bestimmt den Ankerpunkt der Form relativ zum gesetzten
|
* Bestimmt die Koordinaten des angegebenen Ankers der Form relativ zum
|
||||||
* {@link #setAnchor(Options.Direction) Ankerpunkt}.
|
* aktuellen {@link #setAnchor(Options.Direction) Ankerpunkt}.
|
||||||
*
|
*
|
||||||
* @param anchor Die Richtung des Ankerpunktes.
|
* @param anchor Die Richtung des Ankers.
|
||||||
* @return Der relative Ankerpunkt.
|
* @return Der relative Ankerpunkt.
|
||||||
|
* @see #getAnchorPoint(double, double, Options.Direction)
|
||||||
*/
|
*/
|
||||||
public Point2D.Double getAnchorPoint( Options.Direction anchor ) {
|
public Point2D.Double getAnchorPoint( Options.Direction anchor ) {
|
||||||
double wHalf = getWidth() * .5, hHalf = getHeight() * .5;
|
double wHalf = getWidth() * .5, hHalf = getHeight() * .5;
|
||||||
|
|
||||||
// anchor == CENTER
|
// anchor == CENTER
|
||||||
Point2D.Double anchorPoint = new Point2D.Double(
|
return new Point2D.Double(
|
||||||
wHalf * (anchor.x - this.anchor.x),
|
wHalf * (anchor.x - this.anchor.x),
|
||||||
hHalf * (anchor.y - this.anchor.y)
|
hHalf * (anchor.y - this.anchor.y)
|
||||||
);
|
);
|
||||||
|
|
||||||
return anchorPoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ermittelt die absoluten Koordinaten eines angegebenen
|
* Ermittelt die absoluten Koordinaten des angegebenen
|
||||||
* {@link #setAnchor(Options.Direction) Ankers}.
|
* {@link #setAnchor(Options.Direction) Ankers}.
|
||||||
|
* <p>
|
||||||
|
* Die absoluten Koordinaten werden bestimmt durch die Position der Form
|
||||||
|
* {@code (x,y)} plus die
|
||||||
|
* {@link #getAnchorPoint(Options.Direction) relativen Koordinaten} des
|
||||||
|
* Ankers.
|
||||||
*
|
*
|
||||||
* @param anchor Die Richtung des Ankerpunktes.
|
* <b>Wichtig:</b> Die Berechnung berücksichtigt derzeit keine Rotationen
|
||||||
|
* und Transformationen der Form.
|
||||||
|
*
|
||||||
|
* @param anchor Die Richtung des Ankers.
|
||||||
* @return Der absolute Ankerpunkt.
|
* @return Der absolute Ankerpunkt.
|
||||||
|
* @see #getAnchorPoint(double, double, Options.Direction)
|
||||||
*/
|
*/
|
||||||
public Point2D.Double getAbsAnchorPoint( Options.Direction anchor ) {
|
public Point2D.Double getAbsAnchorPoint( Options.Direction anchor ) {
|
||||||
// TODO: Die absoluten Anker müssten eigentlich die Rotation berücksichtigen.
|
// TODO: Die absoluten Anker müssten eigentlich die Rotation berücksichtigen.
|
||||||
@@ -329,20 +380,22 @@ public abstract class Shape extends FilledShape {
|
|||||||
/**
|
/**
|
||||||
* Kopiert die Eigenschaften der angegebenen Form in diese.
|
* Kopiert die Eigenschaften der angegebenen Form in diese.
|
||||||
* <p>
|
* <p>
|
||||||
* Unterklassen sollten diese Methode überschreiben, um weitere
|
* Unterklassen überschreiben diese Methode, um weitere Eigenschaften zu
|
||||||
* Eigenschaften zu kopieren (zum Beispiel den Radius eines Kreises).
|
* kopieren (zum Beispiel den Radius eines Kreises). Überschreibende
|
||||||
* Unterklassen sollten immer mit dem Aufruf {@code super.copyFrom(shape)}
|
* Methoden sollten immer mit dem Aufruf {@code super.copyFrom(shape)} die
|
||||||
* die Basiseigenschaften kopieren.
|
* Basiseigenschaften kopieren.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Methode sollte so viele Eigenschaften wie möglich von der anderen
|
* Die Methode kopiert so viele Eigenschaften wie möglich von der
|
||||||
* Form in diese kopieren. Wenn die andere Form einen anderen Typ hat, dann
|
* angegebenen Form in diese. Wenn die andere Form einen anderen Typ hat,
|
||||||
* werden trotzdem die Basiseigenschaften (Konturlinie, Füllung, Position,
|
* dann werden trotzdem die Basiseigenschaften (Konturlinie, Füllung,
|
||||||
* Rotation, Skalierung, Sichtbarkeit und Ankerpunkt) in diese Form kopiert.
|
* Position, Rotation, Skalierung, Sichtbarkeit und Ankerpunkt) in diese
|
||||||
* Implementierende Unterklassen können soweit sinnvoll auch andere Werte
|
* Form kopiert. Soweit sinnvoll übernehmen implementierende Unterklassen
|
||||||
* übernehmen. Eine {@link Ellipse} kann beispielsweise auch die Breite und
|
* auch andere Werte. Eine {@link Ellipse} kopiert beispielsweise auch die
|
||||||
* Höhe eines {@link Rectangle} übernehmen.
|
* Breite und Höhe eines {@link Rectangle}.
|
||||||
|
* <p>
|
||||||
|
* Wird {@code null} übergeben, dann passiert nichts.
|
||||||
*
|
*
|
||||||
* @param shape Die Originalform, von der kopiert werden soll.
|
* @param shape Die Originalform, von der kopiert wird.
|
||||||
*/
|
*/
|
||||||
public void copyFrom( Shape shape ) {
|
public void copyFrom( Shape shape ) {
|
||||||
if( shape != null ) {
|
if( shape != null ) {
|
||||||
@@ -352,8 +405,8 @@ public abstract class Shape extends FilledShape {
|
|||||||
setStrokeWeight(shape.getStrokeWeight());
|
setStrokeWeight(shape.getStrokeWeight());
|
||||||
setStrokeType(shape.getStrokeType());
|
setStrokeType(shape.getStrokeType());
|
||||||
visible = shape.isVisible();
|
visible = shape.isVisible();
|
||||||
rotation = shape.rotation;
|
rotation = shape.getRotation();
|
||||||
scale(shape.scale);
|
scale(shape.getScale());
|
||||||
setAnchor(shape.getAnchor());
|
setAnchor(shape.getAnchor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,9 +424,9 @@ public abstract class Shape extends FilledShape {
|
|||||||
* </code></pre>
|
* </code></pre>
|
||||||
* <p>
|
* <p>
|
||||||
* Die Methode kann beliebig umgesetzt werden, um eine 1-zu-1-Kopie dieser
|
* Die Methode kann beliebig umgesetzt werden, um eine 1-zu-1-Kopie dieser
|
||||||
* Form zu erhalten. In der Regel sollte aber jede Form einen Konstruktor
|
* Form zu erhalten. In der Regel besitzt aber jede Form einen Konstruktor,
|
||||||
* besitzen, die alle Werte einer andern Form übernimmt. Die gezeigte
|
* der alle Werte einer andern Form übernimmt. Die gezeigte Implementierung
|
||||||
* Implementierung dürfte daher im Regelfall ausreichend sein.
|
* ist daher im Regelfall ausreichend.
|
||||||
*
|
*
|
||||||
* @return Eine genaue Kopie dieser Form.
|
* @return Eine genaue Kopie dieser Form.
|
||||||
*/
|
*/
|
||||||
@@ -384,19 +437,10 @@ public abstract class Shape extends FilledShape {
|
|||||||
* zurück. Intern werden die AWT Shapes benutzt, um sie auf den
|
* zurück. Intern werden die AWT Shapes benutzt, um sie auf den
|
||||||
* {@link Graphics2D Grafikkontext} zu zeichnen.
|
* {@link Graphics2D Grafikkontext} zu zeichnen.
|
||||||
* <p>
|
* <p>
|
||||||
* Da die AWT-Shape bei jedem Zeichnen (also mindestens einmal pro Frame)
|
* Wenn diese Form nicht durch eine AWT-Shape dargestellt wird, liefert die
|
||||||
* benötigt wird, wird das aktuelle Shape-Objekt in {@link #awtShape}
|
* Methode {@code null}.
|
||||||
* zwischengespeichert. Bei Änderungen der Objekteigenschaften muss daher
|
|
||||||
* intern {@link #invalidate()} aufgerufen werden, damit beim nächsten
|
|
||||||
* Aufruf von {@link #draw(Graphics2D)} die Shape mit einem Aufurf von
|
|
||||||
* {@code getShape()} neu erzeugt wird. Unterklassen können aber auch die
|
|
||||||
* zwischengespeicherte Shape direkt modifizieren, um eine Neugenerierung zu
|
|
||||||
* vermeiden.
|
|
||||||
* <p>
|
|
||||||
* Wenn diese Form nicht durch eine AWT-Shape dargstellt wird, kann die
|
|
||||||
* Methode {@code null} zurückgeben.
|
|
||||||
*
|
*
|
||||||
* @return Eine Java-AWT {@code Shape} die diess Form repräsentiert oder
|
* @return Eine Java-AWT {@code Shape} die diese Form repräsentiert oder
|
||||||
* {@code null}.
|
* {@code null}.
|
||||||
*/
|
*/
|
||||||
public abstract java.awt.Shape getShape();
|
public abstract java.awt.Shape getShape();
|
||||||
@@ -414,34 +458,85 @@ public abstract class Shape extends FilledShape {
|
|||||||
return new Bounds(this);
|
return new Bounds(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verschiebt die Form um die angegebenen Werte entlang der
|
||||||
|
* Koordinatenachsen.
|
||||||
|
*
|
||||||
|
* @param dx Verschiebung entlang der x-Achse.
|
||||||
|
* @param dy Verschiebung entlang der y-Achse.
|
||||||
|
*/
|
||||||
public void move( double dx, double dy ) {
|
public void move( double dx, double dy ) {
|
||||||
x += dx;
|
x += dx;
|
||||||
y += dy;
|
y += dy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bewegt die Form an die angegebenen Koordinaten.
|
||||||
|
*
|
||||||
|
* @param x Die neue x-Koordinate.
|
||||||
|
* @param y Die neue y-Koordinate.
|
||||||
|
*/
|
||||||
public void moveTo( double x, double y ) {
|
public void moveTo( double x, double y ) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bewegt die Form an dieselben Koordinaten wie die angegebene Form.
|
||||||
|
*
|
||||||
|
* @param shape Eine andere Form.
|
||||||
|
*/
|
||||||
public void moveTo( Shape shape ) {
|
public void moveTo( Shape shape ) {
|
||||||
moveTo(shape.getX(), shape.getY());
|
moveTo(shape.getX(), shape.getY());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bewegt die Form zum angegebenen Ankerpunkt der angegebenen Form.
|
||||||
|
*
|
||||||
|
* @param shape Die andere Form.
|
||||||
|
* @param dir Die Richtung des Ankerpunktes.
|
||||||
|
* @see #moveTo(Shape, Options.Direction, double)
|
||||||
|
*/
|
||||||
public void moveTo( Shape shape, Options.Direction dir ) {
|
public void moveTo( Shape shape, Options.Direction dir ) {
|
||||||
moveTo(shape, dir, 0.0);
|
moveTo(shape, dir, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bewegt den Ankerpunkt dieser Form zu einem Ankerpunkt einer anderen Form.
|
* Bewegt den Ankerpunkt dieser Form zu einem Ankerpunkt einer anderen
|
||||||
* Mit {@code buff} kann ein zusätzlicher Abstand angegeben werden, um den
|
* Form.
|
||||||
* die Form entlang des Ankerpunktes {@code anchor} verschoben werden soll.
|
* <p>
|
||||||
|
* Mit {@code buff} wird ein zusätzlicher Abstand angegeben, um den die Form
|
||||||
|
* entlang des Ankerpunktes {@code anchor} verschoben wird.
|
||||||
|
* <p>
|
||||||
* Ist der Anker zum Beispiel {@code NORTH}, dann wird die Form um
|
* Ist der Anker zum Beispiel {@code NORTH}, dann wird die Form um
|
||||||
* {@code buff} nach oben verschoben.
|
* {@code buff} oberhalb der oberen Kante der zweiten Form verschoben.
|
||||||
|
* <p>
|
||||||
|
* Befinden sich die Formen zuvor in folgender Ausrichtung:
|
||||||
|
* <pre>
|
||||||
|
* ┌─────────┐
|
||||||
|
* │ │
|
||||||
|
* W B │
|
||||||
|
* ┌─────┐ │ │
|
||||||
|
* │ │ └─────────┘
|
||||||
|
* W A │
|
||||||
|
* │ │
|
||||||
|
* └─────┘
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* bringt sie der Aufruf {@code B.moveTo(A, DOWN, 0)} in diese Ausrichtung:
|
||||||
|
* <pre>
|
||||||
|
* B.moveTo(A, WEST, 0) │ B.moveTo(A, WEST, 10)
|
||||||
|
* │
|
||||||
|
* ┌─────┬───┐ │ ┌┬────┬────┐
|
||||||
|
* │ │ │ │ ││ │ │
|
||||||
|
* │ A B│ │ │ ││ A B │
|
||||||
|
* │ │ │ │ ││ │ │
|
||||||
|
* └─────┴───┘ │ └┴────┴────┘
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param shape
|
* @param shape Die andere Form.
|
||||||
* @param dir
|
* @param dir Die Richtung des Ankerpunktes.
|
||||||
* @param buff
|
* @param buff Der Abstand zum angegebenen Ankerpunkt.
|
||||||
*/
|
*/
|
||||||
public void moveTo( Shape shape, Options.Direction dir, double buff ) {
|
public void moveTo( Shape shape, Options.Direction dir, double buff ) {
|
||||||
Point2D ap = shape.getAbsAnchorPoint(dir);
|
Point2D ap = shape.getAbsAnchorPoint(dir);
|
||||||
@@ -451,15 +546,22 @@ public abstract class Shape extends FilledShape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Richtet die Form entlang der angegebenen Richtung am Rand der
|
* Bewegt die Form an den Rand der Zeichenfläche in der angegebenen
|
||||||
* Zeichenfläche aus.
|
* Richtung.
|
||||||
*
|
*
|
||||||
* @param dir Die Richtung der Ausrichtung.
|
* @param dir Die Richtung.
|
||||||
*/
|
*/
|
||||||
public void alignTo( Options.Direction dir ) {
|
public void alignTo( Options.Direction dir ) {
|
||||||
alignTo(dir, 0.0);
|
alignTo(dir, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bewegt die Form mit dem angegebenen Abstand an den Rand der Zeichenfläche
|
||||||
|
* in der angegebenen Richtung aus.
|
||||||
|
*
|
||||||
|
* @param dir Die Richtung.
|
||||||
|
* @param buff Der Abstand zum Rand.
|
||||||
|
*/
|
||||||
public void alignTo( Options.Direction dir, double buff ) {
|
public void alignTo( Options.Direction dir, double buff ) {
|
||||||
Point2D anchorShape = Shape.getAnchorPoint(canvasWidth, canvasHeight, dir);
|
Point2D anchorShape = Shape.getAnchorPoint(canvasWidth, canvasHeight, dir);
|
||||||
Point2D anchorThis = this.getAbsAnchorPoint(dir);
|
Point2D anchorThis = this.getAbsAnchorPoint(dir);
|
||||||
@@ -468,20 +570,58 @@ public abstract class Shape extends FilledShape {
|
|||||||
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bewegt den Ankerpunkt dieser Form in der angegebenen Richtung zum
|
||||||
|
* Gleichen Ankerpunkt der anderen Form.
|
||||||
|
*
|
||||||
|
* @param shape Die andere Form.
|
||||||
|
* @param dir Die Richtung des Ankerpunktes.
|
||||||
|
* @see #alignTo(Shape, Options.Direction, double)
|
||||||
|
*/
|
||||||
public void alignTo( Shape shape, Options.Direction dir ) {
|
public void alignTo( Shape shape, Options.Direction dir ) {
|
||||||
alignTo(shape, dir, 0.0);
|
alignTo(shape, dir, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Richtet die Form entlang der angegebenen Richtung an einer anderen Form
|
* Richtet die Form entlang der angegebenen Richtung an einer anderen Form
|
||||||
* aus. Für {@code DOWN} wird beispielsweise die y-Koordinate der unteren
|
* aus.
|
||||||
* Kante dieser Form an der unteren Kante der angegebenen Form {@code shape}
|
* <p>
|
||||||
* ausgerichtet. Die x-Koordinate wird nicht verändert. {@code buff} gibt
|
* {@code buff} gibt einen Abstand ab, um den diese From versetzt
|
||||||
* einen Abstand ab, um den diese From versetzt ausgerichtet werden soll.
|
* ausgerichtet wird.
|
||||||
|
* <p>
|
||||||
|
* Für {@link #DOWN} wird beispielsweise die y-Koordinate der unteren Kante
|
||||||
|
* dieser Form an der unteren Kante von {@code shape} ausgerichtet. Die
|
||||||
|
* x-Koordinate wird in dem Fall nicht verändert.
|
||||||
|
* <p>
|
||||||
|
* Befinden sich die Formen beispielsweise in folgender Position:
|
||||||
|
* <pre>
|
||||||
|
* ┌─────┐
|
||||||
|
* │ │
|
||||||
|
* │ B │
|
||||||
|
* ┌─────┐ │ │
|
||||||
|
* │ │ └──D──┘
|
||||||
|
* │ A │
|
||||||
|
* │ │
|
||||||
|
* └──D──┘
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* <p>
|
||||||
|
* werden sie durch {@code alignTo} so positioniert:
|
||||||
|
* <pre>
|
||||||
|
* B.alignTo(A, EAST, 0) │ B.alignTo(A, EAST, 10)
|
||||||
|
* │
|
||||||
|
* ┌─────┐ ┌─────┐ │ ┌─────┐
|
||||||
|
* │ │ │ │ │ │ │ ┌─────┐
|
||||||
|
* │ A │ │ B │ │ │ A │ │ │
|
||||||
|
* │ │ │ │ │ │ │ │ B │
|
||||||
|
* └──D──┘ └──D──┘ │ └──D──┘ │ │
|
||||||
|
* │ └──D──┘
|
||||||
|
* │
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param shape
|
* @param shape Die andere Form.
|
||||||
* @param dir
|
* @param dir Die Richtung.
|
||||||
* @param buff
|
* @param buff Der Abstand.
|
||||||
*/
|
*/
|
||||||
public void alignTo( Shape shape, Options.Direction dir, double buff ) {
|
public void alignTo( Shape shape, Options.Direction dir, double buff ) {
|
||||||
Point2D anchorShape = shape.getAbsAnchorPoint(dir);
|
Point2D anchorShape = shape.getAbsAnchorPoint(dir);
|
||||||
@@ -491,16 +631,46 @@ public abstract class Shape extends FilledShape {
|
|||||||
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param shape
|
||||||
|
* @param dir
|
||||||
|
* @see #nextTo(Shape, Options.Direction, double)
|
||||||
|
*/
|
||||||
public void nextTo( Shape shape, Options.Direction dir ) {
|
public void nextTo( Shape shape, Options.Direction dir ) {
|
||||||
nextTo(shape, dir, DEFAULT_BUFFER);
|
nextTo(shape, dir, DEFAULT_BUFFER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bewegt die Form neben eine andere in Richtung des angegebenen
|
* Bewegt die Form neben eine andere in Richtung des angegebenen
|
||||||
* Ankerpunktes. Im Gegensatz zu
|
* Ankerpunktes.
|
||||||
* {@link #moveTo(Shape, Options.Direction, double)} wird die Breite bzw.
|
* <p>
|
||||||
* Höhe der Formen berücksichtigt und die Formen so platziert, dass keine
|
* Im Gegensatz zu {@link #moveTo(Shape, Options.Direction, double)} wird
|
||||||
* Überlappungen vorhanden sind.
|
* die Breite bzw. Höhe der Formen berücksichtigt und die Formen so
|
||||||
|
* platziert, dass keine Überlappungen vorhanden sind.
|
||||||
|
* <p>
|
||||||
|
* Befinden sich die Formen zuvor in folgender Ausrichtung:
|
||||||
|
* <pre>
|
||||||
|
* ┌─────┐
|
||||||
|
* │ │
|
||||||
|
* W B │
|
||||||
|
* ┌──────┐ │ │
|
||||||
|
* │ │ └─────┘
|
||||||
|
* │ A E
|
||||||
|
* │ │
|
||||||
|
* └──────┘
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* bringt sie der Aufruf {@code B.nextTo(A, EAST, 0)} in diese Ausrichtung:
|
||||||
|
* <pre>
|
||||||
|
* B.nextTo(A, EAST, 0) │ B.nextTo(A, EAST, 10)
|
||||||
|
* │
|
||||||
|
* ┌─────┬─────┐ │ ┌─────┐ ┌─────┐
|
||||||
|
* │ │ │ │ │ │ │ │
|
||||||
|
* │ A │ B │ │ │ A │ │ B │
|
||||||
|
* │ │ │ │ │ │ │ │
|
||||||
|
* └─────┴─────┘ │ └─────┘ └─────┘
|
||||||
|
* │
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param shape
|
* @param shape
|
||||||
* @param dir
|
* @param dir
|
||||||
@@ -514,6 +684,19 @@ public abstract class Shape extends FilledShape {
|
|||||||
this.y += (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
this.y += (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to, Options.Direction dir ) {
|
||||||
|
Point2D apDir = getAbsAnchorPoint(dir);
|
||||||
|
Point2D apInv = getAbsAnchorPoint(dir.inverse());
|
||||||
|
setGradient(apInv.getX(), apInv.getY(), from, apDir.getX(), apDir.getY(), to);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to ) {
|
||||||
|
Point2D ap = getAbsAnchorPoint(CENTER);
|
||||||
|
setGradient(ap.getX(), ap.getY(), Math.min(ap.getX(), ap.getY()), from, to);
|
||||||
|
}
|
||||||
|
|
||||||
/*public void shear( double dx, double dy ) {
|
/*public void shear( double dx, double dy ) {
|
||||||
verzerrung.shear(dx, dy);
|
verzerrung.shear(dx, dy);
|
||||||
}*/
|
}*/
|
||||||
@@ -560,7 +743,7 @@ public abstract class Shape extends FilledShape {
|
|||||||
shape = transform.createTransformedShape(shape);
|
shape = transform.createTransformedShape(shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
Color currentColor = graphics.getColor();
|
java.awt.Color currentColor = graphics.getColor();
|
||||||
fillShape(shape, graphics);
|
fillShape(shape, graphics);
|
||||||
strokeShape(shape, graphics);
|
strokeShape(shape, graphics);
|
||||||
graphics.setColor(currentColor);
|
graphics.setColor(currentColor);
|
||||||
@@ -573,8 +756,8 @@ public abstract class Shape extends FilledShape {
|
|||||||
* verglichen. Unterklassen überschreiben die Methode, um weitere
|
* verglichen. Unterklassen überschreiben die Methode, um weitere
|
||||||
* Eigenschaften zu berücksichtigen.
|
* Eigenschaften zu berücksichtigen.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Eigenschaften von {@link FilledShape} und {@link StrokedShape} werden
|
* Die Eigenschaften, die durch {@link Fillable} und {@link Strokeable}
|
||||||
* nicht verglichen.
|
* impliziert werden, werden nicht verglichen.
|
||||||
*
|
*
|
||||||
* @param o Ein anderes Objekt.
|
* @param o Ein anderes Objekt.
|
||||||
* @return
|
* @return
|
||||||
@@ -590,4 +773,43 @@ public abstract class Shape extends FilledShape {
|
|||||||
Double.compare(pShape.scale, scale) == 0;
|
Double.compare(pShape.scale, scale) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hilfsmethode für Unterklassen, um die angegebene Form mit den aktuellen
|
||||||
|
* Kontureigenschaften auf den Grafik-Kontext zu zeichnen. Die Methode
|
||||||
|
* verändert gegebenenfalls die aktuelle Farbe des Grafikobjekts und setzt
|
||||||
|
* sie nicht auf den Ursprungswert zurück, wie von {@link #draw(Graphics2D)}
|
||||||
|
* gefordert. Dies sollte die aufrufende Unterklasse übernehmen.
|
||||||
|
*
|
||||||
|
* @param shape Die zu zeichnende Java-AWT Form
|
||||||
|
* @param graphics Das Grafikobjekt.
|
||||||
|
*/
|
||||||
|
protected void strokeShape( java.awt.Shape shape, Graphics2D graphics ) {
|
||||||
|
if( strokeColor != null && strokeColor.getAlpha() > 0
|
||||||
|
&& strokeWeight > 0.0 ) {
|
||||||
|
graphics.setColor(strokeColor.getJavaColor());
|
||||||
|
graphics.setStroke(getStroke());
|
||||||
|
graphics.draw(shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hilfsmethode für Unterklassen, um die angegebene Form mit der aktuellen
|
||||||
|
* Füllung auf den Grafik-Kontext zu zeichnen. Die Methode verändert
|
||||||
|
* gegebenenfalls die aktuelle Farbe des Grafikobjekts und setzt sie nicht
|
||||||
|
* auf den Ursprungswert zurück, wie von {@link #draw(Graphics2D)}
|
||||||
|
* gefordert. Dies sollte die aufrufende Unterklasse übernehmen.
|
||||||
|
*
|
||||||
|
* @param shape Die zu zeichnende Java-AWT Form
|
||||||
|
* @param graphics Das Grafikobjekt.
|
||||||
|
*/
|
||||||
|
protected void fillShape( java.awt.Shape shape, Graphics2D graphics ) {
|
||||||
|
if( fill != null ) {
|
||||||
|
graphics.setPaint(fill);
|
||||||
|
graphics.fill(shape);
|
||||||
|
} else if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
||||||
|
graphics.setColor(fillColor.getJavaColor());
|
||||||
|
graphics.fill(shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.awt.geom.AffineTransform;
|
|||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ import java.util.List;
|
|||||||
* {@link #arrangeInGrid(int, Options.Direction, double, int)} und
|
* {@link #arrangeInGrid(int, Options.Direction, double, int)} und
|
||||||
* {@link #align(Options.Direction)} verwendet werden, die jeweils die Position
|
* {@link #align(Options.Direction)} verwendet werden, die jeweils die Position
|
||||||
* der Formen in der Gruppe ändern und nicht die Position der Gruppe selbst (so
|
* der Formen in der Gruppe ändern und nicht die Position der Gruppe selbst (so
|
||||||
* wie z.B. {@link #alignTo(Shape, Options.Direction)}.
|
* wie z.B. {@link #alignTo(Shape, Options.Direction)}).
|
||||||
*/
|
*/
|
||||||
public class ShapeGroup extends Shape {
|
public class ShapeGroup extends Shape {
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ public class ShapeGroup extends Shape {
|
|||||||
|
|
||||||
public static final int ARRANGE_COLS = 1;
|
public static final int ARRANGE_COLS = 1;
|
||||||
|
|
||||||
private List<Shape> shapes;
|
private final List<Shape> shapes;
|
||||||
|
|
||||||
private double groupWidth = -1.0;
|
private double groupWidth = -1.0;
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ public class ShapeGroup extends Shape {
|
|||||||
|
|
||||||
public ShapeGroup() {
|
public ShapeGroup() {
|
||||||
super();
|
super();
|
||||||
shapes = new ArrayList<>(10);
|
shapes = Collections.synchronizedList(new ArrayList<>(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapeGroup( double x, double y ) {
|
public ShapeGroup( double x, double y ) {
|
||||||
@@ -62,9 +63,7 @@ public class ShapeGroup extends Shape {
|
|||||||
public ShapeGroup( double x, double y, Shape... shapes ) {
|
public ShapeGroup( double x, double y, Shape... shapes ) {
|
||||||
super(x, y);
|
super(x, y);
|
||||||
this.shapes = new ArrayList<>(shapes.length);
|
this.shapes = new ArrayList<>(shapes.length);
|
||||||
for( Shape pShape : shapes ) {
|
Collections.addAll(this.shapes, shapes);
|
||||||
this.shapes.add(pShape);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Shape copy() {
|
public Shape copy() {
|
||||||
@@ -99,9 +98,11 @@ public class ShapeGroup extends Shape {
|
|||||||
|
|
||||||
public <ShapeType extends Shape> List<ShapeType> getShapes( Class<ShapeType> typeClass ) {
|
public <ShapeType extends Shape> List<ShapeType> getShapes( Class<ShapeType> typeClass ) {
|
||||||
LinkedList<ShapeType> list = new LinkedList<>();
|
LinkedList<ShapeType> list = new LinkedList<>();
|
||||||
for( Shape s : shapes ) {
|
synchronized( shapes ) {
|
||||||
if( typeClass.isInstance(s) ) {
|
for( Shape shape : shapes ) {
|
||||||
list.add((ShapeType) s);
|
if( typeClass.isInstance(shape) ) {
|
||||||
|
list.add(typeClass.cast(shape));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
@@ -170,19 +171,15 @@ public class ShapeGroup extends Shape {
|
|||||||
int rows, cols;
|
int rows, cols;
|
||||||
if( mode == ARRANGE_ROWS ) {
|
if( mode == ARRANGE_ROWS ) {
|
||||||
rows = n;
|
rows = n;
|
||||||
cols = (int) ceil(shapes.size() / n);
|
cols = (int) ceil(shapes.size() / (double)n);
|
||||||
} else {
|
} else {
|
||||||
cols = n;
|
cols = n;
|
||||||
rows = (int) ceil(shapes.size() / n);
|
rows = (int) ceil(shapes.size() / (double)n);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate grid cell size
|
// Calculate grid cell size
|
||||||
double maxHeight = shapes.stream().mapToDouble(
|
double maxHeight = shapes.stream().mapToDouble(Shape::getHeight).reduce(0.0, Double::max);
|
||||||
( s ) -> s.getHeight()
|
double maxWidth = shapes.stream().mapToDouble(Shape::getWidth).reduce(0.0, Double::max);
|
||||||
).reduce(0.0, Double::max);
|
|
||||||
double maxWidth = shapes.stream().mapToDouble(
|
|
||||||
( s ) -> s.getWidth()
|
|
||||||
).reduce(0.0, Double::max);
|
|
||||||
double halfHeight = maxHeight * .5;
|
double halfHeight = maxHeight * .5;
|
||||||
double halfWidth = maxWidth * .5;
|
double halfWidth = maxWidth * .5;
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ public class Text extends Shape {
|
|||||||
public Text( double x, double y, String text ) {
|
public Text( double x, double y, String text ) {
|
||||||
this(x, y, text, new Font(Font.SANS_SERIF, Font.PLAIN, DEFAULT_FONTSIZE));
|
this(x, y, text, new Font(Font.SANS_SERIF, Font.PLAIN, DEFAULT_FONTSIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Text( double x, double y, String text, String fontname ) {
|
public Text( double x, double y, String text, String fontname ) {
|
||||||
super(x, y);
|
super(x, y);
|
||||||
Font userfont = FontLoader.loadFont(fontname);
|
Font userfont = FontLoader.loadFont(fontname);
|
||||||
@@ -60,29 +61,58 @@ public class Text extends Shape {
|
|||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFont( String fontname ) {
|
public Font getFont() {
|
||||||
Font newFont = FontLoader.loadFont(fontname);
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFont( Font newFont ) {
|
||||||
|
//font = newFont.deriveFont(font.getStyle(), font.getSize2D());
|
||||||
|
font = new Font(newFont.getFontName(), newFont.getStyle(), newFont.getSize());
|
||||||
|
//font = newFont;
|
||||||
|
calculateBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFont( String fontName ) {
|
||||||
|
Font newFont = FontLoader.loadFont(fontName);
|
||||||
if( newFont != null ) {
|
if( newFont != null ) {
|
||||||
setFont(newFont);
|
setFont(newFont);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFont( Font newFont ) {
|
public void setFont( String fontName, double fontSize ) {
|
||||||
font = newFont.deriveFont(font.getSize2D());
|
Font newFont = FontLoader.loadFont(fontName);
|
||||||
|
if( newFont != null ) {
|
||||||
|
setFont(newFont);
|
||||||
|
}
|
||||||
|
font = newFont.deriveFont((float) fontSize);
|
||||||
calculateBounds();
|
calculateBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Font getFont() {
|
public void setFont( String fontName, double fontSize, int style ) {
|
||||||
return font;
|
Font newFont = FontLoader.loadFont(fontName);
|
||||||
|
if( newFont != null ) {
|
||||||
|
setFont(newFont);
|
||||||
|
}
|
||||||
|
font = newFont.deriveFont(style, (float) fontSize);
|
||||||
|
calculateBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFontsize( double size ) {
|
public double getFontSize() {
|
||||||
|
return font.getSize2D();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFontSize( double size ) {
|
||||||
font = font.deriveFont((float) size);
|
font = font.deriveFont((float) size);
|
||||||
calculateBounds();
|
calculateBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getFontsize() {
|
public int getFontStyle() {
|
||||||
return font.getSize2D();
|
return font.getStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFontStyle( int fontStyle ) {
|
||||||
|
font = font.deriveFont(fontStyle);
|
||||||
|
calculateBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getText() {
|
public String getText() {
|
||||||
@@ -151,19 +181,19 @@ public class Text extends Shape {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void copyFrom( Shape shape ) {
|
public void copyFrom( Shape shape ) {
|
||||||
super.copyFrom(shape);
|
|
||||||
if( shape instanceof Text ) {
|
if( shape instanceof Text ) {
|
||||||
Text pText = (Text) shape;
|
Text pText = (Text) shape;
|
||||||
this.text = pText.getText();
|
this.text = pText.getText();
|
||||||
this.font = pText.getFont();
|
this.font = pText.getFont();
|
||||||
calculateBounds();
|
|
||||||
}
|
}
|
||||||
|
super.copyFrom(shape);
|
||||||
|
calculateBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scale( double factor ) {
|
public void scale( double factor ) {
|
||||||
super.scale(factor);
|
super.scale(factor);
|
||||||
setFontsize(font.getSize2D() * factor);
|
setFontSize(font.getSize2D() * factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -191,7 +221,7 @@ public class Text extends Shape {
|
|||||||
if( strokeColor != null && strokeColor.getAlpha() > 0
|
if( strokeColor != null && strokeColor.getAlpha() > 0
|
||||||
&& strokeWeight > 0.0 ) {
|
&& strokeWeight > 0.0 ) {
|
||||||
graphics.setColor(strokeColor.getJavaColor());
|
graphics.setColor(strokeColor.getJavaColor());
|
||||||
graphics.setStroke(createStroke());
|
graphics.setStroke(getStroke());
|
||||||
graphics.drawRect(0, 0, width, height);
|
graphics.drawRect(0, 0, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class BarChart extends Rectangle {
|
public class BarChart extends Rectangle {
|
||||||
|
|
||||||
public static String DEFAULT_LABEL = "%.2f";
|
public static String DEFAULT_LABEL = "%.2f";
|
||||||
@@ -105,10 +106,10 @@ public class BarChart extends Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addValue( double pValue, Color pColor ) {
|
public void addValue( double pValue, Color pColor ) {
|
||||||
addValue(pValue, pColor, String.format(DEFAULT_LABEL, pValue));
|
addValue(pValue, String.format(DEFAULT_LABEL, pValue), pColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addValue( double pValue, Color pColor, String pLabel ) {
|
public void addValue( double pValue, String pLabel, Color pColor ) {
|
||||||
addValue(new BasicChartValue(pValue, pLabel, pColor));
|
addValue(new BasicChartValue(pValue, pLabel, pColor));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +289,7 @@ public class BarChart extends Rectangle {
|
|||||||
barY += barHeight + gap;
|
barY += barHeight + gap;
|
||||||
}
|
}
|
||||||
graphics.setColor(getStrokeColor().getJavaColor());
|
graphics.setColor(getStrokeColor().getJavaColor());
|
||||||
graphics.setStroke(createStroke());
|
graphics.setStroke(getStroke());
|
||||||
if( inverted ) {
|
if( inverted ) {
|
||||||
graphics.drawLine((int) width, 0, (int) width, (int) height);
|
graphics.drawLine((int) width, 0, (int) width, (int) height);
|
||||||
} else {
|
} else {
|
||||||
@@ -360,7 +361,7 @@ public class BarChart extends Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
graphics.setColor(getStrokeColor().getJavaColor());
|
graphics.setColor(getStrokeColor().getJavaColor());
|
||||||
graphics.setStroke(createStroke());
|
graphics.setStroke(getStroke());
|
||||||
if( inverted ) {
|
if( inverted ) {
|
||||||
graphics.drawLine(0, 0, (int) width, 0);
|
graphics.drawLine(0, 0, (int) width, 0);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -136,57 +136,36 @@ public class BasicChartValue implements ChartValue {
|
|||||||
this.color = color;
|
this.color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public double getX() {
|
public double getX() {
|
||||||
return xValue;
|
return xValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public double getValue() {
|
public double getValue() {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setValue( double pValue ) {
|
public void setValue( double pValue ) {
|
||||||
this.value = pValue;
|
this.value = pValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public String getLabel() {
|
public String getLabel() {
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setLabel( String pLabel ) {
|
public void setLabel( String pLabel ) {
|
||||||
this.label = pLabel;
|
this.label = pLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Color getColor() {
|
public Color getColor() {
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setColor( Color pColor ) {
|
public void setColor( Color pColor ) {
|
||||||
this.color = pColor;
|
this.color = pColor;
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public class ChartAxes extends Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
graphics.setColor(strokeColor.getJavaColor());
|
graphics.setColor(strokeColor.getJavaColor());
|
||||||
graphics.setStroke(createStroke());
|
graphics.setStroke(getStroke());
|
||||||
graphics.drawLine(0, (int)(height), (int)((xMax-xMin) * xUnit), (int)(height));
|
graphics.drawLine(0, (int)(height), (int)((xMax-xMin) * xUnit), (int)(height));
|
||||||
graphics.drawLine(0, (int)(height), 0, (int)(height - (yMax-yMin) * yUnit));
|
graphics.drawLine(0, (int)(height), 0, (int)(height - (yMax-yMin) * yUnit));
|
||||||
if( showArrows ) {
|
if( showArrows ) {
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public class LineChart extends Rectangle {
|
|||||||
for( ChartValue lcv : val ) {
|
for( ChartValue lcv : val ) {
|
||||||
if( drawLines && lastLcv != null ) {
|
if( drawLines && lastLcv != null ) {
|
||||||
graphics.setColor(getStrokeColor().getJavaColor());
|
graphics.setColor(getStrokeColor().getJavaColor());
|
||||||
graphics.setStroke(createStroke());
|
graphics.setStroke(getStroke());
|
||||||
graphics.drawLine((int)(lastLcv.getX()*xUnit), (int)(height - lastLcv.getValue()*yUnit), (int)(lcv.getX()*xUnit), (int)(height - lcv.getValue()*yUnit));
|
graphics.drawLine((int)(lastLcv.getX()*xUnit), (int)(height - lastLcv.getValue()*yUnit), (int)(lcv.getX()*xUnit), (int)(height - lcv.getValue()*yUnit));
|
||||||
drawDot(graphics, lastLcv, xUnit, yUnit);
|
drawDot(graphics, lastLcv, xUnit, yUnit);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ public class PieChart extends Circle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
graphics.setColor(getStrokeColor().getJavaColor());
|
graphics.setColor(getStrokeColor().getJavaColor());
|
||||||
graphics.setStroke(createStroke());
|
graphics.setStroke(getStroke());
|
||||||
graphics.drawOval(0, 0, (int) (radius * 2), (int) (radius * 2));
|
graphics.drawOval(0, 0, (int) (radius * 2), (int) (radius * 2));
|
||||||
|
|
||||||
graphics.setTransform(originalTransform);
|
graphics.setTransform(originalTransform);
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Diese Paket enthält Formen, die Diagramme darstellen.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.shapes.charts;
|
||||||
13
src/main/java/schule/ngb/zm/shapes/package-info.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Implementationen der abstrakten
|
||||||
|
* {@link schule.ngb.zm.shapes.Shape} Klasse.
|
||||||
|
*
|
||||||
|
* Jede Unterklasse von {@code Shape} stellt eine konkrete Form wie ein
|
||||||
|
* {@link schule.ngb.zm.shapes.Rectangle Rechteck}, ein
|
||||||
|
* {@link schule.ngb.zm.shapes.Circle Kreis} oder ein
|
||||||
|
* {@link schule.ngb.zm.shapes.Picture Bild} dar.
|
||||||
|
*
|
||||||
|
* Mit {@link schule.ngb.zm.shapes.ShapeGroup} können Formen gruppiert
|
||||||
|
* und gemeinsam transformiert werden.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.shapes;
|
||||||
@@ -4,55 +4,134 @@ import java.util.Collection;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine Helferklasse, um Dinge zu zählen.
|
||||||
|
* <p>
|
||||||
|
* Im einfachsten Fall kann der Zähler als geteilte Zählvariable genutzt werden,
|
||||||
|
* die mit {@link #inc()} und {@link #dec()} aus verschiedenen Objekten oder
|
||||||
|
* Methoden verändert werden kann.
|
||||||
|
* <p>
|
||||||
|
* Der Zähler kann aber auch Objekte zählen, indem die Instanzen an
|
||||||
|
* {@link #count(Object)} übergeben werden. Am Ende kann mit {@link #getCount()}
|
||||||
|
* die Anzahl der Objekte abgerufen werden.
|
||||||
|
* <p>
|
||||||
|
* Handelt es sich bei den Objekten um Zahlen, dann merkt sich ein
|
||||||
|
* {@code Counter} auch das Maximum, das Minimum, die Summe und den Durchschnitt
|
||||||
|
* der gezählten Werte.
|
||||||
|
* <p>
|
||||||
|
* Ein Zähler kann auch komplette Arrays oder Listen von Zahlen zählen und die
|
||||||
|
* obigen Statistiken auswerten.
|
||||||
|
*/
|
||||||
@SuppressWarnings( "unused" )
|
@SuppressWarnings( "unused" )
|
||||||
public final class Counter {
|
public final class Counter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen neuen {@code Counter}, der alle Integer im angegebenen
|
||||||
|
* Array gezählt hat.
|
||||||
|
*
|
||||||
|
* @param values Die zu zählenden Werte.
|
||||||
|
* @return Ein neuer {@code Counter}.
|
||||||
|
*/
|
||||||
|
public static Counter fromArray( int[] values ) {
|
||||||
|
return new Counter().countAll(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen neuen {@code Counter}, der alle Doubles im angegebenen
|
||||||
|
* Array gezählt hat.
|
||||||
|
*
|
||||||
|
* @param values Die zu zählenden Werte.
|
||||||
|
* @return Ein neuer {@code Counter}.
|
||||||
|
*/
|
||||||
public static Counter fromArray( double[] values ) {
|
public static Counter fromArray( double[] values ) {
|
||||||
return new Counter().countAll(values);
|
return new Counter().countAll(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen neuen {@code Counter}, der alle Zahlen im angegebenen
|
||||||
|
* Array gezählt hat.
|
||||||
|
*
|
||||||
|
* @param values Die zu zählenden Werte.
|
||||||
|
* @return Ein neuer {@code Counter}.
|
||||||
|
*/
|
||||||
public static Counter fromArray( Number[] values ) {
|
public static Counter fromArray( Number[] values ) {
|
||||||
return new Counter().countAll(values);
|
return new Counter().countAll(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen neuen {@code Counter}, der alle Zahlen in der angegebenen
|
||||||
|
* Liste gezählt hat.
|
||||||
|
*
|
||||||
|
* @param values Die zu zählenden Werte.
|
||||||
|
* @return Ein neuer {@code Counter}.
|
||||||
|
*/
|
||||||
public static Counter fromList( List<Number> values ) {
|
public static Counter fromList( List<Number> values ) {
|
||||||
return new Counter().countAll(values);
|
return new Counter().countAll(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AtomicInteger count = new AtomicInteger(0);
|
/**
|
||||||
|
* Aktuelle Anzahl gezählter Werte.
|
||||||
|
*/
|
||||||
|
private final AtomicInteger count = new AtomicInteger(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statistiken zu den gezählten Werten.
|
||||||
|
*/
|
||||||
private double min = Double.NaN, max = Double.NaN, sum = Double.NaN;
|
private double min = Double.NaN, max = Double.NaN, sum = Double.NaN;
|
||||||
|
|
||||||
public void Counter() {
|
/**
|
||||||
|
* Erstellt einen neuen, leeren {@code Counter}.
|
||||||
|
*/
|
||||||
|
public Counter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Counter( int initial ) {
|
/**
|
||||||
|
* Ertstellt einen neuen {@code Counter}, der mit dem angegebenen Wert
|
||||||
|
* initialisiert ist.
|
||||||
|
*
|
||||||
|
* @param initial Wert des Zählers zu Beginn.
|
||||||
|
*/
|
||||||
|
public Counter( int initial ) {
|
||||||
count.set(initial);
|
count.set(initial);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Die aktuelle Anzahl.
|
||||||
|
*/
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
return count.get();
|
return count.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Das Maxium der bisher gezählten Werte.
|
||||||
|
*/
|
||||||
public double getMax() {
|
public double getMax() {
|
||||||
synchronized( count ) {
|
synchronized( count ) {
|
||||||
return max;
|
return max;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Das Minimum der bisher gezählten Werte.
|
||||||
|
*/
|
||||||
public double getMin() {
|
public double getMin() {
|
||||||
synchronized( count ) {
|
synchronized( count ) {
|
||||||
return min;
|
return min;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Die Summe der bisher gezählten Werte.
|
||||||
|
*/
|
||||||
public double getSum() {
|
public double getSum() {
|
||||||
synchronized( count ) {
|
synchronized( count ) {
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Der Mittelwert der bisher gezählten Werte.
|
||||||
|
*/
|
||||||
public double getAvg() {
|
public double getAvg() {
|
||||||
if( Double.isNaN(sum) ) {
|
if( Double.isNaN(sum) ) {
|
||||||
return Double.NaN;
|
return Double.NaN;
|
||||||
@@ -61,11 +140,27 @@ public final class Counter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt den Zähler auf den angegebenen Wert.
|
||||||
|
* <p>
|
||||||
|
* Die anderen Statistiken werden nicht verändert.
|
||||||
|
*
|
||||||
|
* @param count Der neue Wert des Zählers.
|
||||||
|
* @return Dieser Zähler selbst (method chaining).
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Counter setCount( int count ) {
|
public Counter setCount( int count ) {
|
||||||
this.count.set(count);
|
this.count.set(count);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt den Zähler auf Null.
|
||||||
|
* <p>
|
||||||
|
* Die Statistiken werden auf {@link Double#NaN} gesetzt.
|
||||||
|
*
|
||||||
|
* @return Dieser Zähler selbst (method chaining).
|
||||||
|
*/
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Counter reset() {
|
public Counter reset() {
|
||||||
count.set(0);
|
count.set(0);
|
||||||
@@ -77,18 +172,40 @@ public final class Counter {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erhöht den Zähler um Eins.
|
||||||
|
* <p>
|
||||||
|
* Die anderen Statistiken werden nicht verändert.
|
||||||
|
*
|
||||||
|
* @return Dieser Zähler selbst (method chaining).
|
||||||
|
*/
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Counter inc() {
|
public Counter inc() {
|
||||||
this.count.incrementAndGet();
|
this.count.incrementAndGet();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verringert den Zähler um Eins.
|
||||||
|
* <p>
|
||||||
|
* Die anderen Statistiken werden nicht verändert.
|
||||||
|
*
|
||||||
|
* @return Dieser Zähler selbst (method chaining).
|
||||||
|
*/
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Counter dec() {
|
public Counter dec() {
|
||||||
this.count.decrementAndGet();
|
this.count.decrementAndGet();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zählt den angegebenen Wert.
|
||||||
|
* <p>
|
||||||
|
* Erhöht den Zähler um Eins und aktualisiert die Statistiken.
|
||||||
|
*
|
||||||
|
* @param value Der neue Wert.
|
||||||
|
* @return Dieser Zähler selbst (method chaining).
|
||||||
|
*/
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Counter count( double value ) {
|
public Counter count( double value ) {
|
||||||
inc();
|
inc();
|
||||||
@@ -103,11 +220,27 @@ public final class Counter {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zählt die angegebene Zahl.
|
||||||
|
* <p>
|
||||||
|
* Erhöht den Zähler um Eins und aktualisiert die Statistiken.
|
||||||
|
*
|
||||||
|
* @param num Die neue Zahl.
|
||||||
|
* @return Dieser Zähler selbst (method chaining).
|
||||||
|
*/
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Counter count( Number num ) {
|
public Counter count( Number num ) {
|
||||||
return count(num.doubleValue());
|
return count(num.doubleValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zählt das angegebenen Objekt.
|
||||||
|
* <p>
|
||||||
|
* Erhöht den Zähler um Eins.
|
||||||
|
*
|
||||||
|
* @param obj Ein beliebiges Objekt.
|
||||||
|
* @return Dieser Zähler selbst (method chaining).
|
||||||
|
*/
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Counter count( Object obj ) {
|
public Counter count( Object obj ) {
|
||||||
if( obj instanceof Number ) {
|
if( obj instanceof Number ) {
|
||||||
@@ -118,6 +251,13 @@ public final class Counter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zöhlt alle Werte im angegebenen Array.
|
||||||
|
*
|
||||||
|
* @param values Das Array der neuen Werte.
|
||||||
|
* @return Dieser Zähler selbst (method chaining).
|
||||||
|
* @see #count(double)
|
||||||
|
*/
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public synchronized Counter countAll( double[] values ) {
|
public synchronized Counter countAll( double[] values ) {
|
||||||
for( double value : values ) {
|
for( double value : values ) {
|
||||||
@@ -126,6 +266,28 @@ public final class Counter {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zöhlt alle Werte im angegebenen Array.
|
||||||
|
*
|
||||||
|
* @param values Das Array der neuen Werte.
|
||||||
|
* @return Dieser Zähler selbst (method chaining).
|
||||||
|
* @see #count(double)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
|
public synchronized Counter countAll( int[] values ) {
|
||||||
|
for( double value : values ) {
|
||||||
|
count(value);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zöhlt alle Zahlen im angegebenen Array.
|
||||||
|
*
|
||||||
|
* @param values Das Array der neuen Zahlen.
|
||||||
|
* @return Dieser Zähler selbst (method chaining).
|
||||||
|
* @see #count(Number)
|
||||||
|
*/
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public synchronized Counter countAll( Number[] values ) {
|
public synchronized Counter countAll( Number[] values ) {
|
||||||
for( Number value : values ) {
|
for( Number value : values ) {
|
||||||
@@ -134,6 +296,13 @@ public final class Counter {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zöhlt alle Zahlen in der angegebenen Sammlung.
|
||||||
|
*
|
||||||
|
* @param values Die Sammlung der neuen Zahlen.
|
||||||
|
* @return Dieser Zähler selbst (method chaining).
|
||||||
|
* @see #count(Number)
|
||||||
|
*/
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public synchronized Counter countAll( Collection<Number> values ) {
|
public synchronized Counter countAll( Collection<Number> values ) {
|
||||||
for( Number value : values ) {
|
for( Number value : values ) {
|
||||||
|
|||||||
400
src/main/java/schule/ngb/zm/util/Faker.java
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
package schule.ngb.zm.util;
|
||||||
|
|
||||||
|
import schule.ngb.zm.Color;
|
||||||
|
import schule.ngb.zm.Constants;
|
||||||
|
import schule.ngb.zm.util.io.ImageLoader;
|
||||||
|
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.FontMetrics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.font.LineMetrics;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static java.lang.Math.exp;
|
||||||
|
import static java.lang.Math.log;
|
||||||
|
import static schule.ngb.zm.Constants.random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine Helferklasse, um zufällige Beispieldaten zu erzeugen.
|
||||||
|
* <p>
|
||||||
|
* Die Klasse kann verschiedene Arten realistischer Beispieldaten erzeugen.
|
||||||
|
* Unter anderem Namen, E-Mail-Adressen, Passwörter oder Platzhalter-Bilder.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
|
public final class Faker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL, von der extern generierte Fake-Bilder geladen werden können.
|
||||||
|
* <p>
|
||||||
|
* Die URL wird als Format-String definiert mit zwei {@code %d}
|
||||||
|
* Platzhaltern, die durch die Breite und Höhe des gewünschten Bildes
|
||||||
|
* ersetzt werden.
|
||||||
|
*/
|
||||||
|
public static final String FAKE_IMG_URL = "https://loremflickr.com/%d/%d";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Benutzerdaten.
|
||||||
|
* <p>
|
||||||
|
* Jeder Datensatz besteht aus einem String-Array mit den folgenden Daten
|
||||||
|
* <ul>
|
||||||
|
* <li><code>fakeUser[i][0]</code>: Vorname</li>
|
||||||
|
* <li><code>fakeUser[i][1]</code>: Nachname</li>
|
||||||
|
* <li><code>fakeUser[i][2]</code>: Geschlecht</li>
|
||||||
|
* <li><code>fakeUser[i][3]</code>: Nutzername</li>
|
||||||
|
* <li><code>fakeUser[i][4]</code>: Passwort</li>
|
||||||
|
* <li><code>fakeUser[i][5]</code>: E-Mail</li>
|
||||||
|
* <li><code>fakeUser[i][6]</code>: Geburtsdatum</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param count Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static String[][] fakeUsers( int count ) {
|
||||||
|
return randomSample("users", count, ( line ) -> line.split(","), String[].class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Vornamen.
|
||||||
|
*
|
||||||
|
* @param count Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static String[] fakeNames( int count ) {
|
||||||
|
return randomSample("users", count, ( line ) -> line.split(",")[0], String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Namen im Format
|
||||||
|
* "Vorname Nachname".
|
||||||
|
*
|
||||||
|
* @param count Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static String[] fakeFullnames( int count ) {
|
||||||
|
return randomSample("users", count, ( line ) -> {
|
||||||
|
String[] parts = line.split(",");
|
||||||
|
return parts[0] + " " + parts[1];
|
||||||
|
}, String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Nutzernamen.
|
||||||
|
*
|
||||||
|
* @param count Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static String[] fakeUsernames( int count ) {
|
||||||
|
return randomSample("users", count, ( line ) -> line.split(",")[3], String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Passwörter.
|
||||||
|
*
|
||||||
|
* @param count Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static String[] fakePasswords( int count ) {
|
||||||
|
return randomSample("users", count, ( line ) -> line.split(",")[4], String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger E-Mail-Adressen.
|
||||||
|
*
|
||||||
|
* @param count Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static String[] fakeEmails( int count ) {
|
||||||
|
return randomSample("users", count, ( line ) -> line.split(",")[5], String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger deutscher
|
||||||
|
* Wörter.
|
||||||
|
*
|
||||||
|
* @param count Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static String[] fakeStrings( int count ) {
|
||||||
|
return randomSample("words", count, ( line ) -> line, String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger
|
||||||
|
* {@code LocalDate}-Objekte, die ein Datum ohne Uhrzeit beschreiben.
|
||||||
|
*
|
||||||
|
* @param count Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static LocalDate[] fakeDates( int count ) {
|
||||||
|
long nowEpoch = LocalDate.now().toEpochDay();
|
||||||
|
long from = LocalDate.ofEpochDay(nowEpoch - 18 * 365).toEpochDay();
|
||||||
|
long to = LocalDate.ofEpochDay(nowEpoch - 14 * 365).toEpochDay();
|
||||||
|
|
||||||
|
LocalDate[] result = new LocalDate[count];
|
||||||
|
for( int i = 0; i < count; i++ ) {
|
||||||
|
result[i] = LocalDate.ofEpochDay((int) Constants.interpolate(from, to, random()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger
|
||||||
|
* {@code LocalDateTime}-Objekte, die einen Zeitpunkt mit Dateum und Uhrzeit
|
||||||
|
* beschreiben,
|
||||||
|
*
|
||||||
|
* @param count Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static LocalDateTime[] fakeDatetimes( int count ) {
|
||||||
|
long nowEpoch = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
|
||||||
|
long from = LocalDateTime.ofEpochSecond(nowEpoch - 18 * 365, 0, ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC);
|
||||||
|
long to = LocalDateTime.ofEpochSecond(nowEpoch - 14 * 365, 0, ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC);
|
||||||
|
|
||||||
|
LocalDateTime[] result = new LocalDateTime[count];
|
||||||
|
for( int i = 0; i < count; i++ ) {
|
||||||
|
result[i] = LocalDateTime.ofEpochSecond((int) Constants.interpolate(from, to, random()), 0, ZoneOffset.UTC);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt einen Blindtext mit der angegebenen Anzahl Worten, aufgeteilt in
|
||||||
|
* die angegebene Anzahl Absätze.
|
||||||
|
* <p>
|
||||||
|
* Abssätze werden duch einen doppelten Zeilenumbruch {@code \n\n}
|
||||||
|
* getrennt.
|
||||||
|
*
|
||||||
|
* @param words Anzahl Wörter im Text insgesamt.
|
||||||
|
* @param paragraphs Anzahl Absätze.
|
||||||
|
* @return Ein zufälliger Blindtext.
|
||||||
|
*/
|
||||||
|
public static String fakeText( int words, int paragraphs ) {
|
||||||
|
String basetext = "";
|
||||||
|
try(
|
||||||
|
InputStream in = Faker.class.getResourceAsStream("mock-text.txt");
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(in))
|
||||||
|
) {
|
||||||
|
basetext = reader.readLine();
|
||||||
|
} catch( IOException ex ) {
|
||||||
|
LOG.error(ex, "Error generating fake blindtext: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = basetext.repeat(words / 283);
|
||||||
|
|
||||||
|
int w = (words % 283);
|
||||||
|
if( w > 0 ) {
|
||||||
|
Matcher m = Pattern.compile("([a-zA-Z_0-9\\u00C0-\\u017F]+\\W+){" + w + "}").matcher(basetext);
|
||||||
|
if( m.find() ) {
|
||||||
|
result += m.group().stripTrailing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( paragraphs > 1 ) {
|
||||||
|
int half = words / paragraphs;
|
||||||
|
final int maxLength = result.length();
|
||||||
|
Matcher m = Pattern.compile("([a-zA-Z_0-9\\u00C0-\\u017F]+\\W+){" + (half - 10) + "," + (half + 10) + "}([a-zA-Z_0-9\\u00C0-\\u017F]+)\\.").matcher(result);
|
||||||
|
if( m.find() ) {
|
||||||
|
result = m.replaceAll(( mr ) -> mr.end() == maxLength ? mr.group().trim() : mr.group().trim() + "\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit der angegebenen Anzahl zufällig erzeugter Integer
|
||||||
|
* im angegebenen Bereich.
|
||||||
|
*
|
||||||
|
* @param count Anzahl der Zahlen im Array.
|
||||||
|
* @param min Untere Grenze der Zufallszahlen.
|
||||||
|
* @param max Obere Grenze der Zufallszahlen.
|
||||||
|
* @return Ein Array mit Zufallszahlen.
|
||||||
|
* @see Constants#random(int, int)
|
||||||
|
*/
|
||||||
|
public static int[] fakeIntArray( int count, int min, int max ) {
|
||||||
|
int[] arr = new int[count];
|
||||||
|
for( int i = 0; i < count; i++ ) {
|
||||||
|
arr[i] = random(min, max);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt eine Liste mit der angegebenen Anzahl zufällig erzeugter Integer
|
||||||
|
* im angegebenen Bereich.
|
||||||
|
* <p>
|
||||||
|
* Ist {@code list} ein Listenobjekt, werden dei Zahlen an diese angehängt.
|
||||||
|
* Wird {@code null} übergeben, wird eine neue {@link ArrayList} erzeugt.
|
||||||
|
*
|
||||||
|
* @param count Anzahl der erzeugten Zahlen.
|
||||||
|
* @param min Untere Grenze der Zufallszahlen.
|
||||||
|
* @param max Obere Grenze der Zufallszahlen.
|
||||||
|
* @param list Eine Liste, die befüllt werden soll, oder {@code null}.
|
||||||
|
* @return Ein Array mit Zufallszahlen.
|
||||||
|
* @see Constants#random(int, int)
|
||||||
|
*/
|
||||||
|
public static List<Integer> fakeIntegerList( int count, int min, int max, List<Integer> list ) {
|
||||||
|
List<Integer> result = (list == null) ? new ArrayList<>(count) : list;
|
||||||
|
fakeIntegers(count, min, max, result::add);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt die angegebene Anzahl Zufallszahlen im angegebenen Bereich und
|
||||||
|
* übergibt sie an den angegebenen {@code Consumer}.
|
||||||
|
*
|
||||||
|
* Ein typischer Aufruf, um eine {@code #LinkedList} mit 100 Zufallszahlen
|
||||||
|
* zu erzeugen könnte so aussehen:
|
||||||
|
* <pre><code>
|
||||||
|
* List<Integer> l = new LinkedList<>();
|
||||||
|
* Faker.fakeIntegers(100, 0, 100, l::add);
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param count Anzahl der erzeugten Zahlen.
|
||||||
|
* @param min Untere Grenze der Zufallszahlen.
|
||||||
|
* @param max Obere Grenze der Zufallszahlen.
|
||||||
|
* @param con {@code Consumer} für die Zahlen.
|
||||||
|
*/
|
||||||
|
public static void fakeIntegers( int count, int min, int max, Consumer<Integer> con ) {
|
||||||
|
for( int i = 0; i < count; i++ ) {
|
||||||
|
con.accept(random(min, max));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <L> L fakeIntegers( int count, int min, int max, Supplier<L> sup, BiConsumer<L, Integer> con ) {
|
||||||
|
L result = sup.get();
|
||||||
|
|
||||||
|
for( int i = 0; i < count; i++ ) {
|
||||||
|
con.accept(result, random(min, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "unchecked" )
|
||||||
|
private static <T> T[] randomSample( String filename, int count, Function<String, T> transformer, Class<T> type ) {
|
||||||
|
T[] result = (T[]) Array.newInstance(type, count);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
double k = count; // cast to double
|
||||||
|
|
||||||
|
double W = exp(log(random()) / k);
|
||||||
|
|
||||||
|
try(
|
||||||
|
InputStream in = Faker.class.getResourceAsStream("mock-" + filename + ".csv");
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(in))
|
||||||
|
) {
|
||||||
|
String line;
|
||||||
|
while( (line = reader.readLine()) != null ) {
|
||||||
|
if( i < count ) {
|
||||||
|
result[i] = transformer.apply(line);
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
int j = (int) (log(random()) / log(1 - W)) + 1;
|
||||||
|
while( j > 0 ) {
|
||||||
|
line = reader.readLine();
|
||||||
|
j -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( line != null ) {
|
||||||
|
result[random(0, count - 1)] = transformer.apply(line);
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
W *= exp(log(random()) / k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill remaining array
|
||||||
|
while( i < count ) {
|
||||||
|
result[i] = result[random(0, i - 1)];
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
} catch( IOException ex ) {
|
||||||
|
LOG.error(ex, "Error loading mock data file: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Platzhalter-Bild in der angegebenen Größe.
|
||||||
|
* <p>
|
||||||
|
* Das Bild ist ein aus dem Internet geladenes, zufälliges Motiv, dass unter
|
||||||
|
* einer freien Lizenz (Creative Commons) steht.
|
||||||
|
*
|
||||||
|
* @param width Breite des Bildes.
|
||||||
|
* @param height Höhe des Bildes.
|
||||||
|
* @return Ein zufälliges Bild in der angegebenen Größe.
|
||||||
|
*/
|
||||||
|
public static BufferedImage fakeImage( int width, int height ) {
|
||||||
|
return fakeImage(width, height, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Platzhalter-Bild in der angegebenen Größe.
|
||||||
|
* <p>
|
||||||
|
* Wenn {@code fromWeb} auf {@code true} gesetzt ist, wird ein zufälliges
|
||||||
|
* Motiv, das unter einer freien Lizenz (Creative Commons) steht, geladen.
|
||||||
|
* Bei {@code false} wird das Bild lokal generiert.
|
||||||
|
*
|
||||||
|
* @param width Breite des Bildes.
|
||||||
|
* @param height Höhe des Bildes.
|
||||||
|
* @param fromWeb Bei {@code true} wird das Bild aus dem Internet geladen,
|
||||||
|
* bei {@code false} wird das Bild lokal erzeugt.
|
||||||
|
* @return Ein zufälliges Bild in der angegebenen Größe.
|
||||||
|
*/
|
||||||
|
public static BufferedImage fakeImage( int width, int height, boolean fromWeb ) {
|
||||||
|
if( !fromWeb ) {
|
||||||
|
BufferedImage img = ImageLoader.createImage(width, height);
|
||||||
|
Graphics2D graphics = (Graphics2D) img.getGraphics().create();
|
||||||
|
|
||||||
|
String text = width + " x " + height;
|
||||||
|
|
||||||
|
Color clr = Constants.randomNiceColor();
|
||||||
|
|
||||||
|
graphics.setBackground(clr.getJavaColor());
|
||||||
|
graphics.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
graphics.setColor(clr.textcolor().getJavaColor());
|
||||||
|
graphics.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, (int) ((width + height) * 0.05)));
|
||||||
|
FontMetrics fontMerics = graphics.getFontMetrics();
|
||||||
|
LineMetrics lineMetrics = fontMerics.getLineMetrics(text, graphics);
|
||||||
|
graphics.drawString(text,
|
||||||
|
(width - fontMerics.stringWidth(text)) / 2,
|
||||||
|
(int) (height / 2 - lineMetrics.getDescent() + lineMetrics.getAscent() / 2)
|
||||||
|
);
|
||||||
|
graphics.dispose();
|
||||||
|
|
||||||
|
return img;
|
||||||
|
} else {
|
||||||
|
return ImageLoader.loadImage(String.format(FAKE_IMG_URL, width, height), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Faker() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Log LOG = Log.getLogger(Faker.class);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -26,7 +26,8 @@ import static java.util.logging.Level.*;
|
|||||||
* Klasse nur genau ein {@code Log}-Objekt erstellt. Mehrere {@code Log}s nutzen
|
* Klasse nur genau ein {@code Log}-Objekt erstellt. Mehrere {@code Log}s nutzen
|
||||||
* dann aber denselben {@code Logger}.
|
* dann aber denselben {@code Logger}.
|
||||||
* <p>
|
* <p>
|
||||||
* Die API orientiert sich lose an <a href="#">Log4j</a> und vereinfacht die
|
* Die API orientiert sich lose an <a
|
||||||
|
* href="https://logging.apache.org/log4j/2.x/">Log4j</a> und vereinfacht die
|
||||||
* Nutzung der Java logging API für die häufigsten Anwendungsfälle.
|
* Nutzung der Java logging API für die häufigsten Anwendungsfälle.
|
||||||
*/
|
*/
|
||||||
public final class Log {
|
public final class Log {
|
||||||
@@ -76,7 +77,7 @@ public final class Log {
|
|||||||
* mindestens herabgesenkt werden sollen.
|
* mindestens herabgesenkt werden sollen.
|
||||||
*/
|
*/
|
||||||
public static void enableGlobalLevel( Level level ) {
|
public static void enableGlobalLevel( Level level ) {
|
||||||
int lvl = Validator.requireNotNull(level).intValue();
|
int lvl = Validator.requireNotNull(level, "level").intValue();
|
||||||
ensureRootLoggerInitialized();
|
ensureRootLoggerInitialized();
|
||||||
|
|
||||||
// Decrease level of root level ConsoleHandlers for output
|
// Decrease level of root level ConsoleHandlers for output
|
||||||
@@ -133,7 +134,6 @@ public final class Log {
|
|||||||
|
|
||||||
if( System.getProperty("java.util.logging.SimpleFormatter.format") == null
|
if( System.getProperty("java.util.logging.SimpleFormatter.format") == null
|
||||||
&& LogManager.getLogManager().getProperty("java.util.logging.SimpleFormatter.format") == null ) {
|
&& LogManager.getLogManager().getProperty("java.util.logging.SimpleFormatter.format") == null ) {
|
||||||
// System.setProperty("java.util.logging.SimpleFormatter.format", DEFAULT_LOG_FORMAT);
|
|
||||||
rootLogger.addHandler(new StreamHandler(System.err, new LogFormatter()) {
|
rootLogger.addHandler(new StreamHandler(System.err, new LogFormatter()) {
|
||||||
@Override
|
@Override
|
||||||
public synchronized void publish( final LogRecord record ) {
|
public synchronized void publish( final LogRecord record ) {
|
||||||
@@ -141,15 +141,6 @@ public final class Log {
|
|||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// rootLogger.setUseParentHandlers(false);
|
|
||||||
}
|
|
||||||
if( rootLogger.getUseParentHandlers() ) {
|
|
||||||
// This logger was not configured somewhere else
|
|
||||||
// Add a Handler and Formatter
|
|
||||||
|
|
||||||
//StreamHandler rootHandler = new StreamHandler(System.out, new SimpleFormatter());
|
|
||||||
//rootLogger.addHandler(rootHandler);
|
|
||||||
//rootLogger.setUseParentHandlers(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGING_INIT = true;
|
LOGGING_INIT = true;
|
||||||
@@ -208,26 +199,15 @@ public final class Log {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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<String> msgSupplier ) {
|
private void doLog( Level level, final Throwable throwable, final Supplier<String> msgSupplier ) {
|
||||||
String clazz = sourceClass.getName();
|
String clazz = sourceClass.getName();
|
||||||
String method = "unknown";
|
String method = "unknown";
|
||||||
|
|
||||||
StackTraceElement[] trace = new Throwable().getStackTrace();
|
StackTraceElement[] trace = new Throwable().getStackTrace();
|
||||||
for( int i = 0; i < trace.length; i++ ) {
|
for( StackTraceElement stackTraceElement : trace ) {
|
||||||
if( !trace[i].getClassName().equals(Log.class.getName()) ) {
|
if( !stackTraceElement.getClassName().equals(Log.class.getName()) ) {
|
||||||
clazz = trace[i].getClassName();
|
clazz = stackTraceElement.getClassName();
|
||||||
method = trace[i].getMethodName();
|
method = stackTraceElement.getMethodName();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,7 +348,7 @@ public final class Log {
|
|||||||
pw.close();
|
pw.close();
|
||||||
throwable = sw.toString();
|
throwable = sw.toString();
|
||||||
}
|
}
|
||||||
return String.format(DEFAULT_LOG_FORMAT,
|
return String.format(DEFAULT_DEBUG_FORMAT,
|
||||||
zdt,
|
zdt,
|
||||||
source,
|
source,
|
||||||
record.getLoggerName(),
|
record.getLoggerName(),
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ package schule.ngb.zm.util;
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zufallsgenerator für Perlin-Noise.
|
* Generator für Perlin-Noise.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Implementierung basiert auf dem von Ken Perlin entwickelten Algorithmus
|
* Die Implementierung basiert auf dem von Ken Perlin entwickelten Algorithmus
|
||||||
* und wurde anhand der <a
|
* und wurde anhand der <a
|
||||||
* href="https://adrianb.io/2014/08/09/perlinnoise.html">Beschreibung von
|
* href="https://adrianb.io/2014/08/09/perlinnoise.html">Beschreibung von
|
||||||
* FLAFLA2</a> implementiert.
|
* FLAFLA2</a> implementiert.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public class Noise {
|
public class Noise {
|
||||||
|
|
||||||
private static final int N = 256;
|
private static final int N = 256;
|
||||||
@@ -17,7 +18,7 @@ public class Noise {
|
|||||||
private static final int M = N - 1;
|
private static final int M = N - 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interne Permutationstabelle für diesen Generator
|
* Interne Permutationstabelle für diesen Generator.
|
||||||
*/
|
*/
|
||||||
private int[] p;
|
private int[] p;
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ public class Noise {
|
|||||||
/**
|
/**
|
||||||
* Initialisiert diesen Perlin-Noise mit dem angegebenen Zufallsgenerator.
|
* Initialisiert diesen Perlin-Noise mit dem angegebenen Zufallsgenerator.
|
||||||
*
|
*
|
||||||
* @param rand
|
* @param rand Ein Zufallsgenerator-Objekt.
|
||||||
*/
|
*/
|
||||||
public Noise( Random rand ) {
|
public Noise( Random rand ) {
|
||||||
init(rand);
|
init(rand);
|
||||||
|
|||||||
@@ -2,77 +2,189 @@ package schule.ngb.zm.util;
|
|||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helferklasse zur Zeitmessung im Nanosekundenbereich.
|
||||||
|
* <p>
|
||||||
|
* Mit einem {@code Timer} kann zum Beispiel die Laufzeit eines Algorithmus
|
||||||
|
* gemessen werden. Wie eine echte Stoppuhr läuft der {@code Timer} weiter, wenn
|
||||||
|
* nach einem {@link #stop()} wieder {@link #start()} aufgerufen wird. Soll die
|
||||||
|
* Zeitmessung wieder bei null beginnen, muss vorher {@link #reset()} genutzt
|
||||||
|
* werden.
|
||||||
|
* <p>
|
||||||
|
* Die gemessene Zeit kann in {@link #getMillis() Millisekunden} oder
|
||||||
|
* {@link #getSeconds() Sekunden} abgerufen werden. Wird eine noch größere
|
||||||
|
* Genauigkeit benötigt, können mit {@link #getTime(TimeUnit)} beliebige
|
||||||
|
* Zeiteinheiten (zum Beispiel {@link TimeUnit#NANOSECONDS Nanosekunden})
|
||||||
|
* abgerufen werden.
|
||||||
|
* <p>
|
||||||
|
* Die Zeit kann auch bei laufender Uhr abgefragt werden. In dem Fall wird die
|
||||||
|
* bis zu diesem Zeitpunkt gemessene Zeit zurückgegeben.
|
||||||
|
*/
|
||||||
@SuppressWarnings( "unused" )
|
@SuppressWarnings( "unused" )
|
||||||
public final class Timer {
|
public final class Timer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Basiseinheit für die Zeitmessung.
|
||||||
|
*/
|
||||||
private final TimeUnit baseUnit;
|
private final TimeUnit baseUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob die Zeitmessung gerade läuft.
|
||||||
|
*/
|
||||||
private boolean running = false;
|
private boolean running = false;
|
||||||
|
|
||||||
private long starttime = -1;
|
/**
|
||||||
|
* Startzeit der Zeitmessung. -1, wenn noch keine Messung gestartet wurde.
|
||||||
|
*/
|
||||||
|
private long start = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeit, die bisher bei allen Zeitmessungen, die gestoppt wurden, insgesamt
|
||||||
|
* vergangen ist.
|
||||||
|
*/
|
||||||
private long elapsed = 0;
|
private long elapsed = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen neuen {@code Timer} mit Millisekunden als Basiseinheit.
|
||||||
|
*/
|
||||||
public Timer() {
|
public Timer() {
|
||||||
this(TimeUnit.MILLISECONDS);
|
this(TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen {@code Timer}, der die angegebene Einheit als Basiseinheit
|
||||||
|
* für {@link #getTime()} benutzt.
|
||||||
|
* <p>
|
||||||
|
* Um eine Zeitmessung in Nanosekunden durchzuführen, kann der {@code Timer}
|
||||||
|
* beispielsweise so instanziiert werden:
|
||||||
|
* <pre><code>
|
||||||
|
* Timer clock = new Timer(TimeUnit.NANOSECONDS);
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param baseUnit Die Basiseinheit für die Zeitmessung.
|
||||||
|
*/
|
||||||
public Timer( TimeUnit baseUnit ) {
|
public Timer( TimeUnit baseUnit ) {
|
||||||
this.baseUnit = baseUnit;
|
this.baseUnit = baseUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob die Zeitmessung gerade läuft.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn die Zeitmessung mit {@link #start()} gestartet
|
||||||
|
* wurde.
|
||||||
|
*/
|
||||||
public boolean isRunning() {
|
public boolean isRunning() {
|
||||||
return running;
|
return running;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Startet die Zeitmessung.
|
||||||
|
* <p>
|
||||||
|
* Wenn zuvor schon eine Zeitmessung gestartet wurde, wird die neue Messung
|
||||||
|
* zur Summe aller Messungen hinzuaddiert. Soll die Messung bei null
|
||||||
|
* starten, muss vorher {@link #reset()} verwendet werden:
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* // Timer auf null stellen und sofort starten
|
||||||
|
* timer.reset().start();
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @return Dieser {@code Timer} selbst (method chaining).
|
||||||
|
*/
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Timer start() {
|
public Timer start() {
|
||||||
starttime = System.nanoTime();
|
start = System.nanoTime();
|
||||||
running = true;
|
running = true;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stoppt den {@code Timer}, wenn er derzeit läuft. Die gemessene Dauer wird
|
||||||
|
* zur Summe aller gemessenen Zeiten hinzuaddiert.
|
||||||
|
*
|
||||||
|
* @return Dieser {@code Timer} selbst (method chaining).
|
||||||
|
*/
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Timer stop() {
|
public Timer stop() {
|
||||||
|
if( running ) {
|
||||||
running = false;
|
running = false;
|
||||||
elapsed += System.nanoTime() - starttime;
|
elapsed += System.nanoTime() - start;
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt den {@code Timer} auf den Startzustand und löscht alle bisher
|
||||||
|
* gemessenen Zeiten. Falls die Zeitmessung gerade läuft, stoppt sie nicht,
|
||||||
|
* sondern startet vom Zeitpunkt des Aufrufs neu.
|
||||||
|
*
|
||||||
|
* @return Dieser {@code Timer} selbst (method chaining).
|
||||||
|
*/
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Timer reset() {
|
public Timer reset() {
|
||||||
running = false;
|
|
||||||
starttime = -1;
|
|
||||||
elapsed = 0;
|
elapsed = 0;
|
||||||
|
start = -1;
|
||||||
|
|
||||||
|
if( running ) {
|
||||||
|
start = System.nanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Zeit in der Basiseinheit zurück.
|
||||||
|
*
|
||||||
|
* @return Die bisher insgesamt gemessene Zeit.
|
||||||
|
*/
|
||||||
public long getTime() {
|
public long getTime() {
|
||||||
return getTime(baseUnit);
|
return getTime(baseUnit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Zeit in der angegebenen Einheit zurück.
|
||||||
|
* <p>
|
||||||
|
* Größere Zeiteinheiten werden gerundet und verlieren daher an Genauigkeit.
|
||||||
|
* Eine Zeitmessung von 999 Millisekunden wird als 0 Sekunden
|
||||||
|
* zurückgegeben.
|
||||||
|
* <p>
|
||||||
|
* Um genauere Ergebnisse zu erhalten, kann mit {@link #getMillis()} und
|
||||||
|
* {@link #getSeconds()} die gemessene Zeit in Minuten beziehungsweise
|
||||||
|
* Sekunden als Kommazahl abgefragt werden.
|
||||||
|
*
|
||||||
|
* @param unit Zeiteinheit
|
||||||
|
* @return Die bisher insgesamt gemessene Zeit in der gewählten Zeiteinheit.
|
||||||
|
*/
|
||||||
public long getTime( TimeUnit unit ) {
|
public long getTime( TimeUnit unit ) {
|
||||||
if( running ) {
|
if( running ) {
|
||||||
return unit.convert(System.nanoTime() - starttime + elapsed, TimeUnit.NANOSECONDS);
|
return unit.convert(System.nanoTime() - start + elapsed, TimeUnit.NANOSECONDS);
|
||||||
} else {
|
} else {
|
||||||
return unit.convert(elapsed, TimeUnit.NANOSECONDS);
|
return unit.convert(elapsed, TimeUnit.NANOSECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die gemessene Zeit in Millisekunden (gerundet) zurück.
|
||||||
|
*
|
||||||
|
* @return Die gemessene Zeit in ms.
|
||||||
|
*/
|
||||||
public int getMillis() {
|
public int getMillis() {
|
||||||
if( running ) {
|
if( running ) {
|
||||||
return (int) ((System.nanoTime() - starttime + elapsed) / 1000000);
|
return (int) ((System.nanoTime() - start + elapsed) / 1000000);
|
||||||
} else {
|
} else {
|
||||||
return (int) (elapsed / 1000000);
|
return (int) (elapsed / 1000000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die gemessene Zeit in Sekunden zurück.
|
||||||
|
*
|
||||||
|
* @return Die gemessene Zeit in s.
|
||||||
|
*/
|
||||||
public double getSeconds() {
|
public double getSeconds() {
|
||||||
if( running ) {
|
if( running ) {
|
||||||
return (System.nanoTime() - starttime + elapsed) / 1000000000.0;
|
return (System.nanoTime() - start + elapsed) / 1000000000.0;
|
||||||
} else {
|
} else {
|
||||||
return elapsed / 1000000000.0;
|
return elapsed / 1000000000.0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,60 @@
|
|||||||
package schule.ngb.zm.util;
|
package schule.ngb.zm.util;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statische Methoden, um Methodenparameter auf Gültigkeit zu prüfen.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( {"unused", "UnusedReturnValue"} )
|
||||||
public class Validator {
|
public class Validator {
|
||||||
|
|
||||||
public static final <T> T requireNotNull( T obj ) {
|
public static final <T> T requireNotNull( T obj, CharSequence paramName ) {
|
||||||
return Objects.requireNonNull(obj);
|
return requireNotNull(obj, () -> "Parameter <%s> may not be null.".format(paramName.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final <T> T requireNotNull( T obj, CharSequence msg ) {
|
public static final <T> T requireNotNull( T obj, CharSequence paramName, CharSequence msg ) {
|
||||||
return Objects.requireNonNull(obj, msg::toString);
|
return requireNotNull(obj, () -> msg.toString().format(paramName.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final <T> T requireNotNull( T obj, Supplier<String> msg ) {
|
public static final <T> T requireNotNull( T obj, Supplier<String> msg ) {
|
||||||
return Objects.requireNonNull(obj, msg);
|
if( obj == null ) {
|
||||||
|
throw new NullPointerException(msg == null ? "Parameter may nut be null." : msg.get());
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String requireNotEmpty( String str ) {
|
public static final String requireNotEmpty( String str, CharSequence paramName ) {
|
||||||
return requireNotEmpty(str, (Supplier<String>)null);
|
return requireNotEmpty(str, () -> String.format("Parameter <%s> may not be empty string (<%s> provided)", paramName, str));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String requireNotEmpty( String str, CharSequence msg ) {
|
public static final String requireNotEmpty( String str, CharSequence paramName, CharSequence msg ) {
|
||||||
return requireNotEmpty(str, msg::toString);
|
return requireNotEmpty(str, () -> msg.toString().format(paramName.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String requireNotEmpty( String str, Supplier<String> msg ) {
|
public static final String requireNotEmpty( String str, Supplier<String> msg ) {
|
||||||
if( str.isEmpty() )
|
if( str.isEmpty() ) {
|
||||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter may not be empty string (<%s> provided)", str) : msg.get());
|
throw new IllegalArgumentException(msg == null ? String.format("Parameter may not be empty string (<%s> provided)", str) : msg.get());
|
||||||
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final <T> T[] requireNotEmpty( T[] arr, CharSequence paramName ) {
|
||||||
|
return requireNotEmpty(arr, () -> String.format("Parameter <%s> may not be empty", paramName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T> T[] requireNotEmpty( T[] arr, CharSequence paramName, CharSequence msg ) {
|
||||||
|
return requireNotEmpty(arr, () -> msg.toString().format(paramName.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T> T[] requireNotEmpty( T[] arr, Supplier<String> msg ) {
|
||||||
|
if( arr.length == 0 )
|
||||||
|
throw new IllegalArgumentException(msg == null ? "Parameter array may not be empty" : msg.get());
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static final int requirePositive( int i ) {
|
public static final int requirePositive( int i ) {
|
||||||
return requirePositive(i, (Supplier<String>) null);
|
return requirePositive(i, (Supplier<String>) null);
|
||||||
}
|
}
|
||||||
@@ -136,21 +159,6 @@ public class Validator {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
public static final <T> T[] requireNotEmpty( T[] arr ) {
|
|
||||||
return requireNotEmpty(arr, (Supplier<String>)null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> T[] requireNotEmpty( T[] arr, CharSequence msg ) {
|
|
||||||
return requireNotEmpty(arr, msg::toString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> T[] requireNotEmpty( T[] arr, Supplier<String> msg ) {
|
|
||||||
if( arr.length == 0 )
|
|
||||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter array may not be empty") : msg.get());
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> T[] requireSize( T[] arr, int size ) {
|
public static final <T> T[] requireSize( T[] arr, int size ) {
|
||||||
return requireSize(arr, size, (Supplier<String>) null);
|
return requireSize(arr, size, (Supplier<String>) null);
|
||||||
}
|
}
|
||||||
@@ -161,21 +169,7 @@ public class Validator {
|
|||||||
|
|
||||||
public static final <T> T[] requireSize( T[] arr, int size, Supplier<String> msg ) {
|
public static final <T> T[] requireSize( T[] arr, int size, Supplier<String> msg ) {
|
||||||
if( arr.length != size )
|
if( arr.length != size )
|
||||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter array must have <%d> elements (<%d> provided)", size) : msg.get());
|
throw new IllegalArgumentException(msg == null ? String.format("Parameter array must have <%d> elements (<%d> provided)", size, arr.length) : msg.get());
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> T[] requireValid( T[] arr ) {
|
|
||||||
return requireValid(arr, (Supplier<String>)null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final<T> T[] requireValid( T[] arr, CharSequence msg ) {
|
|
||||||
return requireValid(arr, msg::toString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> T[] requireValid( T[] arr, Supplier<String> msg ) {
|
|
||||||
if( arr == null || arr.length > 0 )
|
|
||||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter array may not be null or empty") : msg.get());
|
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,68 @@ import java.util.concurrent.ConcurrentMap;
|
|||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementierung einer EventListener-API basierend auf dem Artikel
|
||||||
|
* <a href="https://org.coloradomesa.edu/~wmacevoy/listen/paper.html">Skilled
|
||||||
|
* Listening in Java</a> von Dr. Warren D. MacEvoy.
|
||||||
|
* <p>
|
||||||
|
* Der {@code EventDispatcher} verwaltet eine Liste von angemeldeten Listenern
|
||||||
|
* in einem {@link CopyOnWriteArraySet}, dass besonders für Situationen geeignet
|
||||||
|
* ist, in denen in der Regel wenige Objekte verwaltet werden müssen, auf die
|
||||||
|
* schnell zugegriffen werden soll und die sich selten ändern. Das trifft in den
|
||||||
|
* meisten Fällen auf die Listener eines Objekts zu.
|
||||||
|
* <p>
|
||||||
|
* Um einen {@code EventDispatcher} zu nutzen, muss eine Schnittstelle als
|
||||||
|
* Unterinterface von {@link Listener} erstellt werden, die Methoden für die
|
||||||
|
* einzelnen Events definiert. In der Regel wird eine Methode pro Event
|
||||||
|
* angelegt, aber dies ist nicht unbedingt notwendig.
|
||||||
|
* <p>
|
||||||
|
* Ein Objekt, dass Events für diese Art von Listener erzeugt, erstellt einen
|
||||||
|
* {@code EventDispatcher} und
|
||||||
|
* {@link #registerEventType(String, BiConsumer) registriert} die notwendigen
|
||||||
|
* Events.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* EventDispatcher<MyEvent, MyEventListener> dispatcher = new EventDispatcher<>();
|
||||||
|
* dispatcher.registerEventType("start", (evt, listener) -> listener.started(evt));
|
||||||
|
* dispatcher.registerEventType("stop", (evt, listener) -> listener.stopped(evt));
|
||||||
|
* </code></pre>
|
||||||
|
* <p>
|
||||||
|
* Hier werden zwei Events registriert "start" und "stop". Die Bezeichnung der
|
||||||
|
* Events wird nut intern verwendet. Jedes Event registriert eine Funktion, die
|
||||||
|
* den Aufruf der Listener-Schnittstelle umsetzt. In der Regel ist dies ein
|
||||||
|
* Lambda-Ausdruck von zwei Parametern: dem Event-Objekt vom Typ {@code E} und
|
||||||
|
* der Listener, der aufgerufen werden soll, vom Typ {@code L}.
|
||||||
|
* <p>
|
||||||
|
* Nun können {@code MyEventListener} angemeldet und Events ausgelöst werden.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* public void addEventListener( MyEventListener listener ) {
|
||||||
|
* dispatcher.addListener(listener);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* public void removeEventListener( MyEventListener listener ) {
|
||||||
|
* dispatcher.removeListener(listener);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* public void methodThatCreatesEvents() {
|
||||||
|
* dispatcher.dispatchEvent("start", new MyEvent());
|
||||||
|
* // Do something
|
||||||
|
* dispatcher.dispatchEvent("stop", new MyEvent());
|
||||||
|
* }
|
||||||
|
* </code></pre>
|
||||||
|
* <p>
|
||||||
|
* Siehe {@link schule.ngb.zm.media.AudioListener} und
|
||||||
|
* {@link schule.ngb.zm.media.Music} für ein Beispiel der Verwendung.
|
||||||
|
*
|
||||||
|
* @param <E> Typ der Event-Objekte.
|
||||||
|
* @param <L> Typ der verwendeten Listener-Schnittstelle.
|
||||||
|
*/
|
||||||
public class EventDispatcher<E, L extends Listener<E>> {
|
public class EventDispatcher<E, L extends Listener<E>> {
|
||||||
|
|
||||||
private CopyOnWriteArraySet<L> listeners;
|
private final CopyOnWriteArraySet<L> listeners;
|
||||||
|
|
||||||
private ConcurrentMap<String, BiConsumer<E, L>> eventRegistry;
|
private final ConcurrentMap<String, BiConsumer<E, L>> eventRegistry;
|
||||||
|
|
||||||
public EventDispatcher() {
|
public EventDispatcher() {
|
||||||
listeners = new CopyOnWriteArraySet<>();
|
listeners = new CopyOnWriteArraySet<>();
|
||||||
@@ -19,8 +76,8 @@ public class EventDispatcher<E, L extends Listener<E>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void registerEventType( String eventKey, BiConsumer<E, L> dispatcher ) {
|
public void registerEventType( String eventKey, BiConsumer<E, L> dispatcher ) {
|
||||||
Validator.requireNotNull(eventKey);
|
Validator.requireNotNull(eventKey, "eventKey");
|
||||||
Validator.requireNotNull(dispatcher);
|
Validator.requireNotNull(dispatcher, "dispatcher");
|
||||||
|
|
||||||
if( !eventRegistered(eventKey) ) {
|
if( !eventRegistered(eventKey) ) {
|
||||||
eventRegistry.put(eventKey, dispatcher);
|
eventRegistry.put(eventKey, dispatcher);
|
||||||
@@ -35,6 +92,7 @@ public class EventDispatcher<E, L extends Listener<E>> {
|
|||||||
listeners.remove(listener);
|
listeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public boolean hasListeners() {
|
public boolean hasListeners() {
|
||||||
return !listeners.isEmpty();
|
return !listeners.isEmpty();
|
||||||
}
|
}
|
||||||
@@ -44,14 +102,12 @@ public class EventDispatcher<E, L extends Listener<E>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void dispatchEvent( String eventKey, final E event ) {
|
public void dispatchEvent( String eventKey, final E event ) {
|
||||||
Validator.requireNotNull(eventKey);
|
Validator.requireNotNull(eventKey, "eventKey");
|
||||||
Validator.requireNotNull(event);
|
Validator.requireNotNull(event, "event");
|
||||||
|
|
||||||
if( eventRegistered(eventKey) ) {
|
if( eventRegistered(eventKey) ) {
|
||||||
final BiConsumer<E, L> dispatcher = eventRegistry.get(eventKey);
|
final BiConsumer<E, L> dispatcher = eventRegistry.get(eventKey);
|
||||||
listeners.forEach(( listener ) -> {
|
listeners.forEach(( listener ) -> dispatcher.accept(event, listener));
|
||||||
dispatcher.accept(event, listener);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package schule.ngb.zm.util.events;
|
package schule.ngb.zm.util.events;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generische Basisschnittstelle für die Implementierung von EventListenern.
|
||||||
|
*
|
||||||
|
* @param <E> Typ der Event-Objekte, die von diesem Listener empfangen werden.
|
||||||
|
* @see EventDispatcher
|
||||||
|
*/
|
||||||
public interface Listener<E> {
|
public interface Listener<E> {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Helferklassen, die das Listener-Entwurfsmuster
|
||||||
|
* umsetzen.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.util.events;
|
||||||
@@ -2,86 +2,196 @@ package schule.ngb.zm.util.io;
|
|||||||
|
|
||||||
import schule.ngb.zm.util.Log;
|
import schule.ngb.zm.util.Log;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.util.ArrayList;
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helferklasse, um Textdateien in verschiedenen Formaten einzulesen.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public final class FileLoader {
|
public final class FileLoader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Charset: ASCII
|
||||||
|
*/
|
||||||
public static final Charset ASCII = StandardCharsets.US_ASCII;
|
public static final Charset ASCII = StandardCharsets.US_ASCII;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Charset: UTF-8
|
||||||
|
*/
|
||||||
public static final Charset UTF8 = StandardCharsets.UTF_8;
|
public static final Charset UTF8 = StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Charset: UTF-16
|
||||||
|
*/
|
||||||
public static final Charset UTF16 = StandardCharsets.UTF_16;
|
public static final Charset UTF16 = StandardCharsets.UTF_16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Charset: ISO-8859-1
|
||||||
|
*/
|
||||||
public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
|
public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt die angegebene Textdatei zeilenweise in eine Liste.
|
||||||
|
* <p>
|
||||||
|
* Als {@link Charset} wird {@link #UTF8} verwendet.
|
||||||
|
*
|
||||||
|
* @param source Die Quelle der Textdatei.
|
||||||
|
* @return Eine Liste mit den Zeilen der Textdatei.
|
||||||
|
* @see #loadLines(String, Charset)
|
||||||
|
*/
|
||||||
public static List<String> loadLines( String source ) {
|
public static List<String> loadLines( String source ) {
|
||||||
return loadLines(source, UTF8);
|
return loadLines(source, UTF8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt die angegebene Textdatei mit dem angegebenen Charset zeilenweise in
|
||||||
|
* eine Liste.
|
||||||
|
* <p>
|
||||||
|
* Am Ende jeder Zeile wird das Symbol für einen Zeilenumbruch ({@code \n})
|
||||||
|
* entfernt.
|
||||||
|
*
|
||||||
|
* @param source Die Quelle der Textdatei.
|
||||||
|
* @param charset Das zu verwendene {@code Charset}.
|
||||||
|
* @return Eine Liste mit den Zeilen der Textdatei.
|
||||||
|
*/
|
||||||
public static List<String> loadLines( String source, Charset charset ) {
|
public static List<String> loadLines( String source, Charset charset ) {
|
||||||
try {
|
try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) {
|
||||||
return Files.readAllLines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset);
|
List<String> result = new ArrayList<>();
|
||||||
} catch( IOException | URISyntaxException ex ) {
|
|
||||||
|
String line;
|
||||||
|
while( (line = reader.readLine()) != null ) {
|
||||||
|
result.add(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch( IOException ex ) {
|
||||||
LOG.error(ex, "Error while loading lines from source <%s>", source);
|
LOG.error(ex, "Error while loading lines from source <%s>", source);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.EMPTY_LIST;
|
/**
|
||||||
}
|
* Lädt den Inhalt der angegebenen Textdatei vollständig in einen String.
|
||||||
|
* <p>
|
||||||
|
* Als {@link Charset} wird {@link #UTF8} verwendet.
|
||||||
|
*
|
||||||
|
* @param source Eine Quelle für die Textdatei (Absoluter Dateipfad,
|
||||||
|
* Dateipfad im Classpath oder Netzwerkressource)
|
||||||
|
* @return Der Inhalt der Textdatei.
|
||||||
|
*/
|
||||||
public static String loadText( String source ) {
|
public static String loadText( String source ) {
|
||||||
return loadText(source, UTF8);
|
return loadText(source, UTF8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt den Inhalt der angegebenen Textdatei mit dem angegebenen
|
||||||
|
* {@code Charset} vollständig in einen String.
|
||||||
|
*
|
||||||
|
* @param source Eine Quelle für die Textdatei (Absoluter Dateipfad,
|
||||||
|
* Dateipfad im Classpath oder Netzwerkressource)
|
||||||
|
* @param charset Der {@code Charset} der Textdatei.
|
||||||
|
* @return Der Inhalt der Textdatei.
|
||||||
|
*/
|
||||||
public static String loadText( String source, Charset charset ) {
|
public static String loadText( String source, Charset charset ) {
|
||||||
try {
|
try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) {
|
||||||
return Files.readString(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset);
|
StringBuilder result = new StringBuilder();
|
||||||
} catch( IOException | URISyntaxException ex ) {
|
|
||||||
LOG.error(ex, "Error while loading text from source <%s>", source);
|
String line;
|
||||||
|
while( (line = reader.readLine()) != null ) {
|
||||||
|
result.append(line).append('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
} catch( IOException ex ) {
|
||||||
|
LOG.error(ex, "Error while loading string from source <%s>", source);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt die Daten aus einer CSV-Datei in ein zweidimensionales
|
||||||
|
* String-Array.
|
||||||
|
* <p>
|
||||||
|
* Der Aufruf entspricht
|
||||||
|
* <pre><code>
|
||||||
|
* FileLoader.loadCsv(source, ',', skipFirst, UTF8);
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param source Eine Quelle für die CSV-Datei (Absoluter Dateipfad,
|
||||||
|
* * Dateipfad im Classpath oder Netzwerkressource)
|
||||||
|
* @param skipFirst Ob die erste Zeile übersprungen werden soll.
|
||||||
|
* @return Ein Array mit den Daten als {@code String}s.
|
||||||
|
* @see #loadCsv(String, char, boolean, Charset)
|
||||||
|
*/
|
||||||
public static String[][] loadCsv( String source, boolean skipFirst ) {
|
public static String[][] loadCsv( String source, boolean skipFirst ) {
|
||||||
return loadCsv(source, ',', skipFirst, UTF8);
|
return loadCsv(source, ',', skipFirst, UTF8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt die Daten aus einer CSV-Datei in ein zweidimensionales
|
||||||
|
* String-Array.
|
||||||
|
* <p>
|
||||||
|
* Die Methode ist nicht in der Lage, komplexe CSV-Dateien zu verarbeiten.
|
||||||
|
* Insbesondere werden Inhalte, die das Trennzeichen {@code separator}
|
||||||
|
* enthalten, nicht korrekt erkannt. Das Trennzeichen wird unabhängig
|
||||||
|
* vom Kontext immer als Zelltrenner erkannt. (Im Normalfall kann das
|
||||||
|
* Trennzeichen durch die Verwendung doppelter Anführungszeichen in der Art
|
||||||
|
* {@code Inhalt,"Inhalt, der Komma enthält",Inhalt} maskiert werden.)
|
||||||
|
* <p>
|
||||||
|
* Es wird auch keine erweiterte Inhaltserkennung ausgeführt, sondern alle
|
||||||
|
* Inhalte als {@code String} gelesen. Die weitere Verarbeitung mit den
|
||||||
|
* passenden Parser-Methoden (beispielsweise
|
||||||
|
* {@link Double#parseDouble(String)}) obligt dem Nutzer.
|
||||||
|
*
|
||||||
|
* @param source Die Quelle der CSV-Daten.
|
||||||
|
* @param separator Das verwendete Trennzeichen.
|
||||||
|
* @param skipFirst Ob die erste Zeile übersprungen werden soll.
|
||||||
|
* @param charset Die zu verwendende Zeichenkodierung.
|
||||||
|
* @return Ein Array mit den Daten als {@code String}s.
|
||||||
|
*/
|
||||||
public static String[][] loadCsv( String source, char separator, boolean skipFirst, Charset charset ) {
|
public static String[][] loadCsv( String source, char separator, boolean skipFirst, Charset charset ) {
|
||||||
try {
|
|
||||||
int n = skipFirst ? 1 : 0;
|
int n = skipFirst ? 1 : 0;
|
||||||
return Files
|
List<String> lines = loadLines(source, charset);
|
||||||
.lines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset)
|
return lines.stream().skip(n).map(
|
||||||
.skip(n)
|
//( line ) -> line.split(Character.toString(separator))
|
||||||
.map(
|
( line ) -> line.split("\\s*" + separator + "\\s*")
|
||||||
( line ) -> line.split(Character.toString(separator))
|
|
||||||
).toArray(String[][]::new);
|
).toArray(String[][]::new);
|
||||||
} catch( IOException | URISyntaxException ex ) {
|
|
||||||
LOG.error(ex, "Error while loading csv source <%s>", source);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new String[0][0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double[][] loadValues( String source, char separator, boolean skipFirst ) {
|
public static double[][] loadValues( String source, char separator, boolean skipFirst ) {
|
||||||
return loadValues(source, separator, skipFirst, UTF8);
|
return loadValues(source, separator, skipFirst, UTF8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt Double-Werte aus einer CSV Datei in ein zweidimensionales Array.
|
||||||
|
* <p>
|
||||||
|
* Die gelesenen Strings werden mit {@link Double#parseDouble(String)} in
|
||||||
|
* {@code double} umgeformt. Es leigt in der Verantwortung des Nutzers
|
||||||
|
* sicherzustellen, dass die CSV-Datei auch nur Zahlen enthält, die korrekt
|
||||||
|
* in {@code double} umgewandelt werden können. Zellen für die die
|
||||||
|
* Umwandlung fehlschlägt werden mit 0.0 befüllt.
|
||||||
|
* <p>
|
||||||
|
* Die Methode unterliegt denselben Einschränkungen wie
|
||||||
|
* {@link #loadCsv(String, char, boolean, Charset)}.
|
||||||
|
*
|
||||||
|
* @param source Die Quelle der CSV-Daten.
|
||||||
|
* @param separator Das verwendete Trennzeichen.
|
||||||
|
* @param skipFirst Ob die erste Zeile übersprungen werden soll.
|
||||||
|
* @param charset Die zu verwendende Zeichenkodierung.
|
||||||
|
* @return Ein Array mit den Daten als {@code String}s.
|
||||||
|
*/
|
||||||
public static double[][] loadValues( String source, char separator, boolean skipFirst, Charset charset ) {
|
public static double[][] loadValues( String source, char separator, boolean skipFirst, Charset charset ) {
|
||||||
try {
|
|
||||||
int n = skipFirst ? 1 : 0;
|
int n = skipFirst ? 1 : 0;
|
||||||
return Files
|
List<String> lines = loadLines(source, charset);
|
||||||
.lines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset)
|
return lines.stream().skip(n).map(
|
||||||
.skip(n)
|
|
||||||
.map(
|
|
||||||
( line ) -> Arrays
|
( line ) -> Arrays
|
||||||
.stream(line.split(Character.toString(separator)))
|
.stream(line.split(Character.toString(separator)))
|
||||||
.mapToDouble(
|
.mapToDouble(
|
||||||
@@ -94,14 +204,9 @@ public final class FileLoader {
|
|||||||
}
|
}
|
||||||
).toArray()
|
).toArray()
|
||||||
).toArray(double[][]::new);
|
).toArray(double[][]::new);
|
||||||
} catch( IOException | URISyntaxException ex ) {
|
|
||||||
LOG.error(ex, "Error while loading double values from csv source <%s>", source);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new double[0][0];
|
private FileLoader() {
|
||||||
}
|
|
||||||
|
|
||||||
public FileLoader() {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Log LOG = Log.getLogger(FileLoader.class);
|
private static final Log LOG = Log.getLogger(FileLoader.class);
|
||||||
|
|||||||
@@ -1,16 +1,25 @@
|
|||||||
package schule.ngb.zm.util.io;
|
package schule.ngb.zm.util.io;
|
||||||
|
|
||||||
import schule.ngb.zm.util.Log;
|
import schule.ngb.zm.util.Log;
|
||||||
|
import schule.ngb.zm.util.Validator;
|
||||||
|
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
import java.awt.FontFormatException;
|
import java.awt.FontFormatException;
|
||||||
|
import java.awt.Image;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine Helferklasse mit Klassenmethoden, um Schriftarten zu laden.
|
||||||
|
* <p>
|
||||||
|
* Schriftarten können von verschiedenen Quellen geladen werden. Schriftarten,
|
||||||
|
* die aus Dateien geladen wurden, werden in einem internen Cache gespeichert
|
||||||
|
* und nachfolgende Versuche, dieselbe Schriftart zu laden, werden aus dem Cache
|
||||||
|
* bedient.
|
||||||
|
*/
|
||||||
public class FontLoader {
|
public class FontLoader {
|
||||||
|
|
||||||
private static final Map<String, Font> fontCache = new ConcurrentHashMap<>();
|
private static final Map<String, Font> fontCache = new ConcurrentHashMap<>();
|
||||||
@@ -18,23 +27,30 @@ public class FontLoader {
|
|||||||
/**
|
/**
|
||||||
* Lädt eine Schrift aus einer Datei.
|
* Lädt eine Schrift aus einer Datei.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Schrift wird unter ihrem Dateinamen in den Schriftenspeicher geladen
|
* Die Methode kann eine Liste von Schriften bekommen und probiert diese
|
||||||
* und kann danach in der Zeichenmaschine benutzt werden.
|
* nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden
|
||||||
*
|
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist
|
||||||
* Ein Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
|
* das Ergebnis {@code null}.
|
||||||
|
* <p>
|
||||||
|
* Die gefundene Schrift wird unter ihrem Dateinamen in den
|
||||||
|
* Schriftenspeicher geladen und kann danach in der Zeichenmaschine benutzt
|
||||||
|
* werden.
|
||||||
|
* <p>
|
||||||
|
* Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
|
||||||
* "Font-Name" geladen und kann danach zum Beispiel in einem
|
* "Font-Name" geladen und kann danach zum Beispiel in einem
|
||||||
* {@link schule.ngb.zm.shapes.Text} mit {@code text.setFont("Font-Name");}
|
* {@link schule.ngb.zm.shapes.Text} mit {@code text.setFont("Font-Name");}
|
||||||
* verwendet werden.
|
* verwendet werden.
|
||||||
*
|
*
|
||||||
* @param source
|
* @param source
|
||||||
* @return
|
* @return Die erste geladene Schrift oder {@code null}.
|
||||||
|
* @see #loadFont(String, String)
|
||||||
*/
|
*/
|
||||||
public static Font loadFont( String source ) {
|
public static Font loadFont( String source ) {
|
||||||
String name = source;
|
String name = source;
|
||||||
// Dateipfad entfernen
|
// Dateipfad entfernen
|
||||||
int lastIndex = source.lastIndexOf(File.separatorChar);
|
int lastIndex = source.lastIndexOf(File.separatorChar);
|
||||||
if( lastIndex > -1 ) {
|
if( lastIndex > -1 ) {
|
||||||
source.substring(lastIndex + 1);
|
source = source.substring(lastIndex + 1);
|
||||||
}
|
}
|
||||||
// Dateiendung entfernen
|
// Dateiendung entfernen
|
||||||
lastIndex = name.lastIndexOf('.');
|
lastIndex = name.lastIndexOf('.');
|
||||||
@@ -45,10 +61,8 @@ public class FontLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Font loadFont( String name, String source ) {
|
public static Font loadFont( String name, String source ) {
|
||||||
Objects.requireNonNull(source, "Font source may not be null");
|
Validator.requireNotNull(source, "Font source may not be null");
|
||||||
if( source.length() == 0 ) {
|
Validator.requireNotEmpty(source, "Font source may not be empty.");
|
||||||
throw new IllegalArgumentException("Font source may not be empty.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if( fontCache.containsKey(name) ) {
|
if( fontCache.containsKey(name) ) {
|
||||||
LOG.trace("Retrieved font <%s> from font cache.", name);
|
LOG.trace("Retrieved font <%s> from font cache.", name);
|
||||||
@@ -57,7 +71,7 @@ public class FontLoader {
|
|||||||
|
|
||||||
// Look for System fonts
|
// Look for System fonts
|
||||||
Font font = Font.decode(source);
|
Font font = Font.decode(source);
|
||||||
if( font != null && source.toLowerCase().contains(font.getFamily().toLowerCase()) ) {
|
if( source.toLowerCase().contains(font.getFamily().toLowerCase()) ) {
|
||||||
fontCache.put(name, font);
|
fontCache.put(name, font);
|
||||||
fontCache.put(source, font);
|
fontCache.put(source, font);
|
||||||
LOG.debug("Loaded system font for <%s>.", source);
|
LOG.debug("Loaded system font for <%s>.", source);
|
||||||
@@ -85,6 +99,39 @@ public class FontLoader {
|
|||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt eine Schrift aus einer Datei.
|
||||||
|
* <p>
|
||||||
|
* Die Methode kann eine Liste von Schriften bekommen und probiert diese
|
||||||
|
* nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden
|
||||||
|
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist
|
||||||
|
* das Ergebnis {@code null}.
|
||||||
|
* <p>
|
||||||
|
* Die gefundene Schrift wird unter ihrem Dateinamen in den
|
||||||
|
* Schriftenspeicher geladen und kann danach in der Zeichenmaschine benutzt
|
||||||
|
* werden.
|
||||||
|
* <p>
|
||||||
|
* Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
|
||||||
|
* "Font-Name" geladen und kann danach zum Beispiel in einem
|
||||||
|
* {@link schule.ngb.zm.shapes.Text} mit {@code text.setFont("Font-Name");}
|
||||||
|
* verwendet werden.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @param sources
|
||||||
|
* @return Die erste geladene Schrift oder {@code null}.
|
||||||
|
* @see #loadFont(String, String)
|
||||||
|
*/
|
||||||
|
public static Font loadFonts( String name, String... sources ) {
|
||||||
|
for( String fontSource : sources ) {
|
||||||
|
// TODO: Ignore exceptions in this case and throw own at end?
|
||||||
|
Font font = loadFont(name, fontSource);
|
||||||
|
if( font != null ) {
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private FontLoader() {
|
private FontLoader() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,13 +18,20 @@ import java.lang.ref.SoftReference;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine Helferklasse mit Klassenmethoden, um Bilder zu laden.
|
||||||
|
* <p>
|
||||||
|
* Bilder können von verschiedenen Quellen als {@link Image} geladen werden. Die
|
||||||
|
* Objekte werden in einem internen Cache gespeichert und nachfolgende Versuche,
|
||||||
|
* dieselbe Quelle zu laden, werden aus dem Cache bedient.
|
||||||
|
*/
|
||||||
public final class ImageLoader {
|
public final class ImageLoader {
|
||||||
|
|
||||||
public static boolean caching = true;
|
public static boolean caching = true;
|
||||||
|
|
||||||
private static Map<String, SoftReference<BufferedImage>> imageCache = new ConcurrentHashMap<>();
|
private static final Map<String, SoftReference<BufferedImage>> imageCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private static SoftReference<BufferedImage> NOCACHE = new SoftReference<>(null);
|
private static final SoftReference<BufferedImage> NOCACHE = new SoftReference<>(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lädt ein Bild von der angegebenen Quelle {@code source}.
|
* Lädt ein Bild von der angegebenen Quelle {@code source}.
|
||||||
@@ -48,12 +55,12 @@ public final class ImageLoader {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Lädt ein Bild von der angegebenen Quelle <var>source</var> und gibt das
|
* Lädt ein Bild von der angegebenen Quelle <var>source</var> und gibt das
|
||||||
* Bild zurück oder {@code null}, wenn das Bild nicht geladen werden
|
* Bild zurück oder {@code null}, wenn das Bild nicht geladen werden konnte.
|
||||||
* konnte. Ist ein Bild mit der angegebenen Quelle im Cache, wird das
|
* Ist ein Bild mit der angegebenen Quelle im Cache, wird das gespeicherte
|
||||||
* gespeicherte Bild zurückgegeben. Dies kann mit {@code caching = false}
|
* Bild zurückgegeben. Dies kann mit {@code caching = false} verhindert
|
||||||
* verhindert werden.
|
* werden.
|
||||||
* <p>
|
* <p>
|
||||||
* Wurde das Chaching global deaktiviert, kann mit <code>caching =
|
* Wurde das Caching global deaktiviert, kann mit <code>caching =
|
||||||
* true</code> das Bild trotzdem aus dem Cache geladen werden, wenn es
|
* true</code> das Bild trotzdem aus dem Cache geladen werden, wenn es
|
||||||
* vorhanden ist.
|
* vorhanden ist.
|
||||||
*
|
*
|
||||||
@@ -113,9 +120,9 @@ public final class ImageLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Speiechert das angegebene Bild unter dem angegebenen Namen im Cache.
|
* Speichert das angegebene Bild unter dem angegebenen Namen im Cache.
|
||||||
* <p>
|
* <p>
|
||||||
* Exisitert zu {@code name} schon ein Bild im Zwischenspeicher
|
* Existiert zu {@code name} schon ein Bild im Zwischenspeicher
|
||||||
* ({@code ImageLoader.isCached(name) == true}), dann wird dieses
|
* ({@code ImageLoader.isCached(name) == true}), dann wird dieses
|
||||||
* überschrieben.
|
* überschrieben.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -149,10 +156,8 @@ public final class ImageLoader {
|
|||||||
* @param name Name des Bildes im Cache.
|
* @param name Name des Bildes im Cache.
|
||||||
*/
|
*/
|
||||||
public static void invalidateCache( final String name ) {
|
public static void invalidateCache( final String name ) {
|
||||||
if( imageCache.containsKey(name) ) {
|
|
||||||
imageCache.remove(name);
|
imageCache.remove(name);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Speichert ein Bild als {@link SoftReference} im Cache.
|
* Speichert ein Bild als {@link SoftReference} im Cache.
|
||||||
@@ -211,7 +216,7 @@ public final class ImageLoader {
|
|||||||
* Bild zurückgegeben und nicht komplett neu geladen.
|
* Bild zurückgegeben und nicht komplett neu geladen.
|
||||||
* <p>
|
* <p>
|
||||||
* <b>Wichtig:</b> Bildreferenzen, die aus dem Cache geladen werden,
|
* <b>Wichtig:</b> Bildreferenzen, die aus dem Cache geladen werden,
|
||||||
* verweisen alle auf dasselbe Objekt. Änderungen schalgen sich daher in
|
* verweisen alle auf dasselbe Objekt. Änderungen schlagen sich daher in
|
||||||
* allen anderen Versionen des Bildes nieder (inklusive dem Bild im
|
* allen anderen Versionen des Bildes nieder (inklusive dem Bild im
|
||||||
* Zwischenspeicher). Für Änderungen sollte daher immer
|
* Zwischenspeicher). Für Änderungen sollte daher immer
|
||||||
* {@link #copyImage(BufferedImage) eine Kopie} des Bildes erstellt werden:
|
* {@link #copyImage(BufferedImage) eine Kopie} des Bildes erstellt werden:
|
||||||
@@ -302,7 +307,7 @@ public final class ImageLoader {
|
|||||||
* @param file Die Zieldatei.
|
* @param file Die Zieldatei.
|
||||||
* @param overwriteIfExists Bei {@code true} wird eine vorhandene Datei
|
* @param overwriteIfExists Bei {@code true} wird eine vorhandene Datei
|
||||||
* überschrieben, bei {@code false} wird eine {@link IOException} geworfen,
|
* überschrieben, bei {@code false} wird eine {@link IOException} geworfen,
|
||||||
* wenn die Datei schon exisitiert.
|
* wenn die Datei schon existiert.
|
||||||
* @throws NullPointerException Falls {@code image} oder {@code file}
|
* @throws NullPointerException Falls {@code image} oder {@code file}
|
||||||
* {@code null} ist.
|
* {@code null} ist.
|
||||||
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
||||||
@@ -335,7 +340,7 @@ public final class ImageLoader {
|
|||||||
* @param file Die Zieldatei.
|
* @param file Die Zieldatei.
|
||||||
* @param overwriteIfExists Bei {@code true} wird eine vorhandene Datei
|
* @param overwriteIfExists Bei {@code true} wird eine vorhandene Datei
|
||||||
* überschrieben, bei {@code false} wird eine {@link IOException} geworfen,
|
* überschrieben, bei {@code false} wird eine {@link IOException} geworfen,
|
||||||
* wenn die Datei schon exisitiert.
|
* wenn die Datei schon existiert.
|
||||||
* @throws NullPointerException Falls {@code image} oder {@code file}
|
* @throws NullPointerException Falls {@code image} oder {@code file}
|
||||||
* {@code null} ist.
|
* {@code null} ist.
|
||||||
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
||||||
|
|||||||
@@ -5,20 +5,72 @@ import schule.ngb.zm.util.Log;
|
|||||||
import schule.ngb.zm.util.Validator;
|
import schule.ngb.zm.util.Validator;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helferklasse, um {@link InputStream}s für Resourcen zu erhalten.
|
* Helferklasse, um {@link InputStream}s für Ressourcen zu erhalten.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class ResourceStreamProvider {
|
public class ResourceStreamProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sucht eine zur angegebenen Quelle passende Resource und öffnet einen
|
* Ermittelt zur angegebenen Quelle einen passenden {@link URL} (<em>Unified
|
||||||
* passenden {@link InputStream}. Die konkrete Art des Streams hängt davon
|
* Resource Locator</em>). Eine passende Datei-Ressource wird wie folgt
|
||||||
* ab, welche Art an Resource gefunden wird:
|
* ermittelt:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>Ist {@code source} eine existierende Datei
|
* <li>Ist {@code source} eine existierende Datei
|
||||||
* {@code new File(source}.isFile() == true}, dann wird ein
|
* ({@code new File(source}.isFile() == true})?</li>
|
||||||
|
* <li>Ist {@code source} ein relativer Pfad im Projekt ({@code getResource(source) != null})?</li>
|
||||||
|
* <li>Ist {@code source} im Classpath enthalten ({@code getClassLoader().getResource(source) != null})?</li>
|
||||||
|
* <li>Ansonsten erstelle ein {@link URL}-Objekt.</li>
|
||||||
|
* </ol>
|
||||||
|
* <p>
|
||||||
|
* Ein {@code URL} für die erste gefundene Resource wird zurückgegeben.
|
||||||
|
* <p>
|
||||||
|
* Auftretende Exceptions werden als {@link IOException} geworfen.
|
||||||
|
* Bei einer Exception werden die nachfolgenden 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 Ressource (Absoluter Dateipfad,
|
||||||
|
* Dateipfad im Classpath oder Netzwerkressource)
|
||||||
|
* @return Ein {@code InputStream} für die Ressource
|
||||||
|
* @throws NullPointerException Falls {@code source} {@code null} ist.
|
||||||
|
* @throws IllegalArgumentException Falls {@code source} ein leerer String
|
||||||
|
* ist.
|
||||||
|
* @throws IOException Geworfen beim Erzeugen einer URL zu
|
||||||
|
* einer bestehenden Ressource.
|
||||||
|
*/
|
||||||
|
public static URL getResourceURL( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
||||||
|
Validator.requireNotNull(source, "Resource source may not be null");
|
||||||
|
Validator.requireNotEmpty(source, "Resource source may not be empty.");
|
||||||
|
|
||||||
|
// Ist source ein valider Dateipfad?
|
||||||
|
File file = new File(source);
|
||||||
|
if( file.isFile() ) {
|
||||||
|
return file.toURI().toURL();
|
||||||
|
}
|
||||||
|
// Ist source im Classpath vorhanden?
|
||||||
|
URL url = Zeichenmaschine.class.getClassLoader().getResource(source);
|
||||||
|
if( url != null ) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
// Dann versuchen aus source direkt eine URL zu machen.
|
||||||
|
return new URL(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sucht eine zur angegebenen Quelle passende Ressource und öffnet einen
|
||||||
|
* passenden {@link InputStream}. Die konkrete Art des Streams hängt davon
|
||||||
|
* ab, welche Art an Ressource gefunden wird:
|
||||||
|
* <ol>
|
||||||
|
* <li>Ist {@code source} eine existierende Datei
|
||||||
|
* {@code new File(source).isFile() == true}, dann wird ein
|
||||||
* {@link FileInputStream} erstellt.</li>
|
* {@link FileInputStream} erstellt.</li>
|
||||||
* <li>Ist {@code source} ein relativer Pfad im Projekt, wird ein passender
|
* <li>Ist {@code source} ein relativer Pfad im Projekt, wird ein passender
|
||||||
* Stream mit {@link Class#getResourceAsStream(String)} geöffnet.</li>
|
* Stream mit {@link Class#getResourceAsStream(String)} geöffnet.</li>
|
||||||
@@ -28,153 +80,130 @@ public class ResourceStreamProvider {
|
|||||||
* {@link URL#openStream() Netwerkstream} geöffnet.</li>
|
* {@link URL#openStream() Netwerkstream} geöffnet.</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* <p>
|
* <p>
|
||||||
* Die Möglichen Resourcen werden in der gelisteten Reihenfolge durchprobiert
|
* Die Möglichen Ressourcen werden in der gelisteten Reihenfolge durchprobiert
|
||||||
* und der erste gefundene Stream zurückgegeben. Auftretende Exceptions
|
* und der erste gefundene Stream zurückgegeben. Auftretende Exceptions
|
||||||
* werden als {@link IOException} geworfen. Das bedeutet, falls für {@code source}
|
* werden als {@link IOException} geworfen. Das bedeutet, falls für {@code source}
|
||||||
* keine gültige Resource gefunden werden kann, wird am Ende die
|
* keine gültige Ressource gefunden werden kann, wird am Ende die
|
||||||
* von {@link URL#openStream()} erzeugte {@code IOException} geworfen.
|
* von {@link URL#openStream()} erzeugte {@code IOException} geworfen.
|
||||||
* <p>
|
* <p>
|
||||||
* Bei einer Exception werden die folgenden Quellen nicht mehr abgefragt.
|
* Bei einer Exception werden die nachfolgenden Quellen nicht mehr abgefragt.
|
||||||
* Eine {@link SecurityException} beim Öffnen des {@code FileInputStream}
|
* Eine {@link SecurityException} beim Öffnen des {@code FileInputStream}
|
||||||
* (zum Beispiel, weil auf eine existierende Datei keine Leserechte bestehen)
|
* (zum Beispiel, weil auf eine existierende Datei keine Leserechte bestehen)
|
||||||
* verhindert daher, dass noch im Classpath gesucht wird.
|
* verhindert daher, dass noch im Classpath gesucht wird.
|
||||||
*
|
*
|
||||||
* @param source Eine Quelle für die Resource (Absoluter Dateipfad,
|
* @param source Eine Quelle für die Ressource (Absoluter Dateipfad,
|
||||||
* Dateipfad im Classpath oder Netzwerkresource)
|
* Dateipfad im Classpath oder Netzwerkresource).
|
||||||
* @return Ein {@code InputStream} für die Resource
|
* @return Ein {@code InputStream} für die Ressource.
|
||||||
* @throws NullPointerException Falls {@code source} {@code null} ist.
|
* @throws NullPointerException Falls {@code source} {@code null} ist.
|
||||||
* @throws IllegalArgumentException Falls {@code source} ein leerer String
|
* @throws IllegalArgumentException Falls {@code source} ein leerer String
|
||||||
* ist.
|
* ist.
|
||||||
* @throws IOException Geworfen beim öffnen des Streams zu
|
* @throws IOException Geworfen beim Öffnen des Streams zu
|
||||||
* einer bestehenden Resource oder falls
|
* einer bestehenden Ressource oder falls
|
||||||
* keine passende Resource gefunden wurde.
|
* keine passende Ressource gefunden
|
||||||
|
* wurde.
|
||||||
*/
|
*/
|
||||||
public static InputStream getInputStream( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
public static InputStream getInputStream( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
||||||
Validator.requireNotNull(source, "Resource source may not be null");
|
return getResourceURL(source).openStream();
|
||||||
Validator.requireNotEmpty(source, "Resource source may not be empty.");
|
|
||||||
|
|
||||||
InputStream in = null;
|
|
||||||
|
|
||||||
// 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
|
|
||||||
if( in == null ) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InputStream getInputStream( File file ) throws IOException {
|
|
||||||
Validator.requireNotNull(file, "Provided file can't be null.");
|
|
||||||
return new FileInputStream(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InputStream getInputStream( URL url ) throws IOException {
|
|
||||||
Validator.requireNotNull(url, "Provided URL can't be null.");
|
|
||||||
return url.openStream();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ermittelt zur angegebenen Quelle einen passenden {@link URL} (<em>Unified
|
* Sucht eine zur angegebenen Quelle passende Ressource und öffnet einen
|
||||||
* Resource Locator</em>). Eine passende Datei-Resource wird wie folgt
|
* passenden {@link OutputStream}.
|
||||||
* ermittelt:
|
|
||||||
* <ol>
|
|
||||||
* <li>Ist {@code source} eine existierende Datei
|
|
||||||
* ({@code new File(source}.isFile() == true})?</li>
|
|
||||||
* <li>Ist {@code source} ein relativer Pfad im Projekt ({@code getResource(source) != null})?.</li>
|
|
||||||
* <li>Ist {@code source} im Classpath enthalten ({@code getClassLoader().getResource(source) != null})?</li>
|
|
||||||
* <li>Ansonten erstellt ein {@link URL}-Objekt.</li>
|
|
||||||
* </ol>
|
|
||||||
* <p>
|
* <p>
|
||||||
* Ein {@code URL} für die erste gefundene Resource wird zurückgegeben.
|
* Es wird davon ausgegangen, dass die Quelle auf eine Datei verweist, auf
|
||||||
* Auftretende Exceptions
|
* die Schreibrechte bestehen. Ist dies nicht der Fall, wird eine
|
||||||
* werden als {@link IOException} geworfen.
|
* {@link IOException} geworfen.
|
||||||
* <p>
|
|
||||||
* 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,
|
* @param source Eine Quelle für die Ressource (Absoluter Dateipfad,
|
||||||
* Dateipfad im Classpath oder Netzwerkresource)
|
* Dateipfad im Classpath).
|
||||||
* @return Ein {@code InputStream} für die Resource
|
* @return {@code OutputStream} für die Ressource.
|
||||||
* @throws NullPointerException Falls {@code source} {@code null} ist.
|
* @throws IOException Falls die Quelle kein gültiges Ziel beschreibt.
|
||||||
* @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 {
|
|
||||||
Validator.requireNotNull(source, "Resource source may not be null");
|
|
||||||
Validator.requireNotEmpty(source, "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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ver
|
|
||||||
* @param source
|
|
||||||
* @return
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
*/
|
||||||
public static OutputStream getOutputStream( String source ) throws IOException {
|
public static OutputStream getOutputStream( String source ) throws IOException {
|
||||||
Validator.requireNotNull(source, "Resource source may not be null");
|
try {
|
||||||
Validator.requireNotEmpty(source, "Resource source may not be empty.");
|
return Files.newOutputStream(Path.of(getResourceURL(source).toURI()));
|
||||||
URL url = getResourceURL(source);
|
} catch( URISyntaxException ex ) {
|
||||||
return getOutputStream(new File(url.getPath()));
|
throw new IOException(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OutputStream getOutputStream( File file ) throws IOException {
|
/**
|
||||||
Validator.requireNotNull(file, "Provided file can't be null.");
|
* Sucht eine zur angegebenen Quelle passende Ressource und öffnet einen
|
||||||
return new FileOutputStream(file);
|
* {@link BufferedReader}.
|
||||||
|
* <p>
|
||||||
|
* Die Suche erfolgt, wie bei {@link #getInputStream(String)} beschrieben.
|
||||||
|
* <p>
|
||||||
|
* Als {@link Charset} wird {@link StandardCharsets#UTF_8 UTF-8} verwendet.
|
||||||
|
*
|
||||||
|
* @param source Eine Quelle für die Ressource (Absoluter Dateipfad,
|
||||||
|
* Dateipfad im Classpath oder Netzwerkresource).
|
||||||
|
* @return Ein {@code BufferedReader} für die Quelle.
|
||||||
|
* @throws IOException Falls die Quelle nicht exisitert oder nicht geöffnet
|
||||||
|
* werden kann.
|
||||||
|
* @see #getInputStream(String)
|
||||||
|
*/
|
||||||
|
public static BufferedReader getReader( String source ) throws IOException {
|
||||||
|
return getReader(source, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Reader getReader( String source ) throws IOException {
|
/**
|
||||||
return new InputStreamReader(getInputStream(source));
|
* Sucht eine zur angegebenen Quelle passende Ressource und öffnet einen
|
||||||
|
* {@link BufferedReader} mit dem angegebenen Charset.
|
||||||
|
* <p>
|
||||||
|
* Die Suche erfolgt wie bei {@link #getInputStream(String)} beschrieben.
|
||||||
|
*
|
||||||
|
* @param source Eine Quelle für die Ressource (Absoluter Dateipfad,
|
||||||
|
* Dateipfad im Classpath oder Netzwerkresource).
|
||||||
|
* @param charset Der zu verwendende Charset.
|
||||||
|
* @return Ein {@code BufferedReader} für die Quelle.
|
||||||
|
* @throws IOException Falls die Quelle nicht exisitert oder nicht geöffnet
|
||||||
|
* werden kann.
|
||||||
|
* @see #getInputStream(String)
|
||||||
|
*/
|
||||||
|
public static BufferedReader getReader( String source, Charset charset ) throws IOException {
|
||||||
|
return new BufferedReader(new InputStreamReader(getInputStream(source), charset.newDecoder()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Writer getWriter( String source ) throws IOException {
|
/**
|
||||||
return new OutputStreamWriter(getOutputStream(source));
|
* Sucht eine zur angegebenen Quelle passende Datei und öffnet einen
|
||||||
|
* {@link BufferedWriter}.
|
||||||
|
* <p>
|
||||||
|
* Die Suche erfolgt wie bei {@link #getOutputStream(String)} beschrieben.
|
||||||
|
* <p>
|
||||||
|
* Als {@link Charset} wird {@link StandardCharsets#UTF_8 UTF-8} verwendet.
|
||||||
|
*
|
||||||
|
* @param source Eine Quelle für die Ressource (Absoluter Dateipfad,
|
||||||
|
* Dateipfad im Classpath oder Netzwerkresource).
|
||||||
|
* @return Ein {@code BufferedWriter} für die Quelle.
|
||||||
|
* @throws IOException Falls die Quelle nicht exisitert oder nicht geöffnet
|
||||||
|
* werden kann.
|
||||||
|
* @see #getOutputStream(String)
|
||||||
|
*/
|
||||||
|
public static BufferedWriter getWriter( String source ) throws IOException {
|
||||||
|
return getWriter(source, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sucht eine zur angegebenen Quelle passende Datei und öffnet einen
|
||||||
|
* {@link BufferedWriter}.
|
||||||
|
* <p>
|
||||||
|
* Die Suche erfolgt wie bei {@link #getOutputStream(String)} beschrieben.
|
||||||
|
* <p>
|
||||||
|
* Als {@link Charset} wird {@link StandardCharsets#UTF_8 UTF-8} verwendet.
|
||||||
|
*
|
||||||
|
* @param source Eine Quelle für die Ressource (Absoluter Dateipfad,
|
||||||
|
* Dateipfad im Classpath oder Netzwerkresource).
|
||||||
|
* @return Ein {@code BufferedWriter} für die Quelle.
|
||||||
|
* @param charset Der zu verwendende Charset.
|
||||||
|
* @throws IOException Falls die Quelle nicht exisitert oder nicht geöffnet
|
||||||
|
* werden kann.
|
||||||
|
* @see #getOutputStream(String)
|
||||||
|
*/
|
||||||
|
public static BufferedWriter getWriter( String source, Charset charset ) throws IOException {
|
||||||
|
return new BufferedWriter(new OutputStreamWriter(getOutputStream(source), charset.newEncoder()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Privater C'tor
|
||||||
private ResourceStreamProvider() {
|
private ResourceStreamProvider() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
5
src/main/java/schule/ngb/zm/util/io/package-info.java
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Helferklassen, um Ressourcen aus verschiedenen Quellen
|
||||||
|
* zu laden.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.util.io;
|
||||||
4
src/main/java/schule/ngb/zm/util/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Helferklassen für verschiedene Einsatzzwecke.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.util;
|
||||||
@@ -5,35 +5,18 @@ import schule.ngb.zm.Zeichenmaschine;
|
|||||||
|
|
||||||
public abstract class FrameSynchronizedTask extends Task {
|
public abstract class FrameSynchronizedTask extends Task {
|
||||||
|
|
||||||
private static Thread mainThread;
|
|
||||||
|
|
||||||
private static final Thread getMainThread() {
|
|
||||||
if( mainThread == null ) {
|
|
||||||
mainThread = Thread.currentThread();
|
|
||||||
if( !mainThread.getName().equals(Constants.APP_NAME) ) {
|
|
||||||
// Need to search for main Zeichenthread ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mainThread;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
initialize();
|
initialize();
|
||||||
running = true;
|
running = true;
|
||||||
|
|
||||||
Object lock = Zeichenmaschine.globalSyncLock;
|
final Object lock = Zeichenmaschine.globalSyncLock;
|
||||||
|
|
||||||
// start of thread in ms
|
|
||||||
final long start = System.currentTimeMillis();
|
|
||||||
// current time in ns
|
// current time in ns
|
||||||
long beforeTime = System.nanoTime();
|
long beforeTime = System.nanoTime();
|
||||||
// store for deltas
|
// internal counters for tick and delta time
|
||||||
long overslept = 0L;
|
int _tick;
|
||||||
// internal counters for tick and runtime
|
double delta;
|
||||||
int _tick = 0;
|
|
||||||
|
|
||||||
double delta = 0.0;
|
|
||||||
|
|
||||||
while( running ) {
|
while( running ) {
|
||||||
// delta in seconds
|
// delta in seconds
|
||||||
@@ -53,10 +36,7 @@ public abstract class FrameSynchronizedTask extends Task {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
running = false;
|
|
||||||
done = true;
|
done = true;
|
||||||
|
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public abstract class RateLimitedTask extends Task {
|
|||||||
// store for deltas
|
// store for deltas
|
||||||
long overslept = 0L;
|
long overslept = 0L;
|
||||||
// delta in ms
|
// delta in ms
|
||||||
double delta = 0;
|
double delta;
|
||||||
|
|
||||||
running = true;
|
running = true;
|
||||||
while( running ) {
|
while( running ) {
|
||||||
@@ -47,10 +47,7 @@ public abstract class RateLimitedTask extends Task {
|
|||||||
// Did we sleep to long?
|
// Did we sleep to long?
|
||||||
overslept = (System.nanoTime() - afterTime) - sleep;
|
overslept = (System.nanoTime() - afterTime) - sleep;
|
||||||
}
|
}
|
||||||
|
|
||||||
running = false;
|
|
||||||
done = true;
|
done = true;
|
||||||
|
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,6 @@ import java.util.concurrent.*;
|
|||||||
*/
|
*/
|
||||||
public class TaskRunner {
|
public class TaskRunner {
|
||||||
|
|
||||||
private static final int POOL_SIZE = 4;
|
|
||||||
|
|
||||||
private static final int SHUTDOWN_TIME = 100;
|
|
||||||
|
|
||||||
|
|
||||||
private static TaskRunner runner;
|
private static TaskRunner runner;
|
||||||
|
|
||||||
public static TaskRunner getTaskRunner() {
|
public static TaskRunner getTaskRunner() {
|
||||||
|
|||||||
5
src/main/java/schule/ngb/zm/util/tasks/package-info.java
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Helferklassen zur Verarbeitung paralleler Aufgaben
|
||||||
|
* innerhalb der Zeichenmaschine.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.util.tasks;
|
||||||
342
src/main/resources/schule/ngb/zm/colornames.csv
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
algae green,#64e986
|
||||||
|
aliceblue,#f0f8ff
|
||||||
|
alien green,#6cc417
|
||||||
|
antiquewhite,#faebd7
|
||||||
|
aquamarine,#7fffd4
|
||||||
|
army brown,#827b60
|
||||||
|
ash gray,#666362
|
||||||
|
avocado green,#b2c248
|
||||||
|
aztech purple,#893bff
|
||||||
|
azure,#f0ffff
|
||||||
|
baby blue,#95b9c7
|
||||||
|
bashful pink,#c25283
|
||||||
|
basket ball orange,#f88158
|
||||||
|
battleship gray,#848482
|
||||||
|
bean red,#f75d59
|
||||||
|
bee yellow,#e9ab17
|
||||||
|
beer,#fbb117
|
||||||
|
beetle green,#4c787e
|
||||||
|
beige,#f5f5dc
|
||||||
|
black cat,#413839
|
||||||
|
black cow,#4c4646
|
||||||
|
black eel,#463e3f
|
||||||
|
black,#000000
|
||||||
|
blanchedalmond,#ffebcd
|
||||||
|
blonde,#fbf6d9
|
||||||
|
blood red,#7e3517
|
||||||
|
blossom pink,#f9b7ff
|
||||||
|
blue angel,#b7ceec
|
||||||
|
blue diamond,#4ee2ec
|
||||||
|
blue dress,#157dec
|
||||||
|
blue eyes,#1569c7
|
||||||
|
blue gray,#98afc7
|
||||||
|
blue green,#7bccb5
|
||||||
|
blue hosta,#77bfc7
|
||||||
|
blue ivy,#3090c7
|
||||||
|
blue jay,#2b547e
|
||||||
|
blue koi,#659ec7
|
||||||
|
blue lagoon,#8eebec
|
||||||
|
blue lotus,#6960ec
|
||||||
|
blue orchid,#1f45fc
|
||||||
|
blue ribbon,#306eff
|
||||||
|
blue whale,#342d7e
|
||||||
|
blue zircon,#57feff
|
||||||
|
blueberry blue,#0041c2
|
||||||
|
blush pink,#e6a9ec
|
||||||
|
blush red,#e56e94
|
||||||
|
brass,#b5a642
|
||||||
|
bright gold,#fdd017
|
||||||
|
bright neon pink,#f433ff
|
||||||
|
bronze,#cd7f32
|
||||||
|
brown bear,#835c3b
|
||||||
|
brown sugar,#e2a76f
|
||||||
|
bullet shell,#af9b60
|
||||||
|
burgundy,#8c001a
|
||||||
|
burlywood,#deb887
|
||||||
|
burnt pink,#c12267
|
||||||
|
butterfly blue,#38acec
|
||||||
|
cadillac pink,#e38aae
|
||||||
|
camel brown,#c19a6b
|
||||||
|
camouflage green,#78866b
|
||||||
|
cantaloupe,#ffa62f
|
||||||
|
caramel,#c68e17
|
||||||
|
carbon gray,#625d5d
|
||||||
|
carnation pink,#f778a1
|
||||||
|
celeste,#50ebec
|
||||||
|
champagne,#f7e7ce
|
||||||
|
charcoal,#34282c
|
||||||
|
chartreuse,#8afb17
|
||||||
|
cherry red,#c24641
|
||||||
|
chestnut red,#c34a2c
|
||||||
|
chestnut,#954535
|
||||||
|
chilli pepper,#c11b17
|
||||||
|
chocolate,#c85a17
|
||||||
|
cinnamon,#c58917
|
||||||
|
cloudy gray,#6d6968
|
||||||
|
clover green,#3ea055
|
||||||
|
cobalt blue,#0020c2
|
||||||
|
coffee,#6f4e37
|
||||||
|
columbia blue,#87afc7
|
||||||
|
construction cone orange,#f87431
|
||||||
|
cookie brown,#c7a317
|
||||||
|
copper,#b87333
|
||||||
|
coral blue,#afdcec
|
||||||
|
coral,#ff7f50
|
||||||
|
corn yellow,#fff380
|
||||||
|
cornflower blue,#6495ed
|
||||||
|
cornsilk,#fff8dc
|
||||||
|
cotton candy,#fcdfff
|
||||||
|
cranberry,#9f000f
|
||||||
|
cream,#ffffcc
|
||||||
|
crimson,#e238ec
|
||||||
|
crocus purple,#9172ec
|
||||||
|
crystal blue,#5cb3ff
|
||||||
|
cyan opaque,#92c7c7
|
||||||
|
cyan or aqua,#00ffff
|
||||||
|
dark carnation pink,#c12283
|
||||||
|
dark forest green,#254117
|
||||||
|
dark goldenrod,#af7817
|
||||||
|
dark orange,#f88017
|
||||||
|
dark orchid,#7d1b7e
|
||||||
|
dark salmon,#e18b6b
|
||||||
|
dark sea green,#8bb381
|
||||||
|
dark slate blue,#2b3856
|
||||||
|
dark slate grey,#25383c
|
||||||
|
dark turquoise,#3b9c9c
|
||||||
|
dark violet,#842dce
|
||||||
|
day sky blue,#82caff
|
||||||
|
deep peach,#ffcba4
|
||||||
|
deep pink,#f52887
|
||||||
|
deep sky blue,#3bb9ff
|
||||||
|
denim blue,#79baec
|
||||||
|
denim dark blue,#151b8d
|
||||||
|
desert sand,#edc9af
|
||||||
|
dimorphotheca magenta,#e3319d
|
||||||
|
dodger blue,#1589ff
|
||||||
|
dollar bill green,#85bb65
|
||||||
|
dragon green,#6afb92
|
||||||
|
dull purple,#7f525d
|
||||||
|
earth blue,#0000a0
|
||||||
|
eggplant,#614051
|
||||||
|
electric blue,#9afeff
|
||||||
|
emerald green,#5ffb17
|
||||||
|
fall leaf brown,#c8b560
|
||||||
|
fern green,#667c26
|
||||||
|
ferrari red,#f70d1a
|
||||||
|
fire engine red,#f62817
|
||||||
|
firebrick,#800517
|
||||||
|
flamingo pink,#f9a7b0
|
||||||
|
forest green,#4e9258
|
||||||
|
frog green,#99c68e
|
||||||
|
ginger brown,#c9be62
|
||||||
|
glacial blue ice,#368bc1
|
||||||
|
golden brown,#eac117
|
||||||
|
goldenrod,#edda74
|
||||||
|
granite,#837e7c
|
||||||
|
grape,#5e5a80
|
||||||
|
grapefruit,#dc381f
|
||||||
|
gray cloud,#b6b6b4
|
||||||
|
gray dolphin,#5c5858
|
||||||
|
gray goose,#d1d0ce
|
||||||
|
gray wolf,#504a4b
|
||||||
|
gray,#736f6e
|
||||||
|
grayish turquoise,#5e7d7e
|
||||||
|
green apple,#4cc417
|
||||||
|
green onion,#6aa121
|
||||||
|
green peas,#89c35c
|
||||||
|
green snake,#6cbb3c
|
||||||
|
green thumb,#b5eaaa
|
||||||
|
green yellow,#b1fb17
|
||||||
|
green,#00ff00
|
||||||
|
greenish blue,#307d7e
|
||||||
|
gunmetal,#2c3539
|
||||||
|
halloween orange,#e66c2c
|
||||||
|
harvest gold,#ede275
|
||||||
|
hazel green,#617c58
|
||||||
|
heliotrope purple,#d462ff
|
||||||
|
hot pink,#f660ab
|
||||||
|
hummingbird green,#7fe817
|
||||||
|
iceberg,#56a5ec
|
||||||
|
iguana green,#9cb071
|
||||||
|
indigo,#4b0082
|
||||||
|
iridium,#3d3c3a
|
||||||
|
jade green,#5efb6e
|
||||||
|
jasmine purple,#a23bec
|
||||||
|
jeans blue,#a0cfec
|
||||||
|
jellyfish,#46c7c7
|
||||||
|
jet gray,#616d7e
|
||||||
|
jungle green,#347c2c
|
||||||
|
kelly green,#4cc552
|
||||||
|
khaki rose,#c5908e
|
||||||
|
khaki,#ada96e
|
||||||
|
lapis blue,#15317e
|
||||||
|
lava red,#e42217
|
||||||
|
lavender blue,#e3e4fa
|
||||||
|
lavender pinocchio,#ebdde2
|
||||||
|
lawn green,#87f717
|
||||||
|
lemon chiffon,#fff8c6
|
||||||
|
light aquamarine,#93ffe8
|
||||||
|
light blue,#addfff
|
||||||
|
light coral,#e77471
|
||||||
|
light cyan,#e0ffff
|
||||||
|
light jade,#c3fdb8
|
||||||
|
light pink,#faafba
|
||||||
|
light salmon,#f9966b
|
||||||
|
light sea green,#3ea99f
|
||||||
|
light sky blue,#82cafa
|
||||||
|
light slate blue,#736aff
|
||||||
|
light slate gray,#6d7b8d
|
||||||
|
light slate,#ccffff
|
||||||
|
light steel blue,#728fce
|
||||||
|
lilac,#c8a2c8
|
||||||
|
lime green,#41a317
|
||||||
|
lipstick pink,#c48793
|
||||||
|
love red,#e41b17
|
||||||
|
lovely purple,#7f38ec
|
||||||
|
macaroni and cheese,#f2bb66
|
||||||
|
macaw blue green,#43bfc7
|
||||||
|
magenta,#ff00ff
|
||||||
|
mahogany,#c04000
|
||||||
|
mango orange,#ff8040
|
||||||
|
marble blue,#566d7e
|
||||||
|
maroon,#810541
|
||||||
|
mauve,#e0b0ff
|
||||||
|
medium aquamarine,#348781
|
||||||
|
medium forest green,#347235
|
||||||
|
medium orchid,#b048b5
|
||||||
|
medium purple,#8467d7
|
||||||
|
medium sea green,#306754
|
||||||
|
medium spring green,#348017
|
||||||
|
medium turquoise,#48cccd
|
||||||
|
medium violet red,#ca226b
|
||||||
|
metallic silver,#bcc6cc
|
||||||
|
midnight blue,#151b54
|
||||||
|
midnight,#2b1b17
|
||||||
|
milk white,#fefcff
|
||||||
|
mint green,#98ff98
|
||||||
|
mist blue,#646d7e
|
||||||
|
misty rose,#fbbbb9
|
||||||
|
moccasin,#827839
|
||||||
|
mocha,#493d26
|
||||||
|
mustard,#ffdb58
|
||||||
|
navy blue,#000080
|
||||||
|
nebula green,#59e817
|
||||||
|
neon pink,#f535aa
|
||||||
|
night,#0c090a
|
||||||
|
northern lights blue,#78c7c7
|
||||||
|
oak brown,#806517
|
||||||
|
ocean blue,#2b65ec
|
||||||
|
oil,#3b3131
|
||||||
|
orange gold,#d4a017
|
||||||
|
orange salmon,#c47451
|
||||||
|
pale blue lily,#cfecec
|
||||||
|
pale violet red,#d16587
|
||||||
|
papaya orange,#e56717
|
||||||
|
parchment,#ffffc2
|
||||||
|
pastel blue,#b4cfec
|
||||||
|
peach,#ffe5b4
|
||||||
|
pearl,#fdeef4
|
||||||
|
periwinkle,#e9cfec
|
||||||
|
pig pink,#fdd7e4
|
||||||
|
pine green,#387c44
|
||||||
|
pink bow,#c48189
|
||||||
|
pink bubblegum,#ffdfdd
|
||||||
|
pink cupcake,#e45e9d
|
||||||
|
pink daisy,#e799a3
|
||||||
|
pink lemonade,#e4287c
|
||||||
|
pink rose,#e7a1b0
|
||||||
|
pink,#faafbe
|
||||||
|
pistachio green,#9dc209
|
||||||
|
platinum,#e5e4e2
|
||||||
|
plum pie,#7d0541
|
||||||
|
plum purple,#583759
|
||||||
|
plum velvet,#7d0552
|
||||||
|
plum,#b93b8f
|
||||||
|
powder blue,#c6deff
|
||||||
|
puce,#7f5a58
|
||||||
|
pumpkin orange,#f87217
|
||||||
|
purple amethyst,#6c2dc7
|
||||||
|
purple daffodil,#b041ff
|
||||||
|
purple dragon,#c38ec7
|
||||||
|
purple flower,#a74ac7
|
||||||
|
purple haze,#4e387e
|
||||||
|
purple iris,#571b7e
|
||||||
|
purple jam,#6a287e
|
||||||
|
purple mimosa,#9e7bff
|
||||||
|
purple monster,#461b7e
|
||||||
|
purple sage bush,#7a5dc7
|
||||||
|
purple,#8e35ef
|
||||||
|
red dirt,#7f5217
|
||||||
|
red fox,#c35817
|
||||||
|
red wine,#990012
|
||||||
|
red,#ff0000
|
||||||
|
robin egg blue,#bdedff
|
||||||
|
rogue pink,#c12869
|
||||||
|
rose gold,#ecc5c0
|
||||||
|
rose,#e8adaa
|
||||||
|
rosy brown,#b38481
|
||||||
|
rosy finch,#7f4e52
|
||||||
|
royal blue,#2b60de
|
||||||
|
rubber ducky yellow,#ffd801
|
||||||
|
ruby red,#f62217
|
||||||
|
rust,#c36241
|
||||||
|
saffron,#fbb917
|
||||||
|
sage green,#848b79
|
||||||
|
salad green,#a1c935
|
||||||
|
sand,#c2b280
|
||||||
|
sandstone,#786d5f
|
||||||
|
sandy brown,#ee9a4d
|
||||||
|
sangria,#7e3817
|
||||||
|
sapphire blue,#2554c7
|
||||||
|
scarlet,#ff2400
|
||||||
|
school bus yellow,#e8a317
|
||||||
|
sea blue,#c2dfff
|
||||||
|
sea green,#4e8975
|
||||||
|
sea turtle green,#438d80
|
||||||
|
seashell,#fff5ee
|
||||||
|
seaweed green,#437c17
|
||||||
|
sedona,#cc6600
|
||||||
|
sepia,#7f462c
|
||||||
|
shamrock green,#347c17
|
||||||
|
shocking orange,#e55b3c
|
||||||
|
sienna,#8a4117
|
||||||
|
silk blue,#488ac7
|
||||||
|
sky blue,#6698ff
|
||||||
|
slate blue,#737ca1
|
||||||
|
slate gray,#657383
|
||||||
|
slime green,#bce954
|
||||||
|
smokey gray,#726e6d
|
||||||
|
spring green,#4aa02c
|
||||||
|
steel blue,#4863a0
|
||||||
|
stoplight go green,#57e964
|
||||||
|
sun yellow,#ffe87c
|
||||||
|
sunrise orange,#e67451
|
||||||
|
tan brown,#ece5b6
|
||||||
|
tangerine,#e78a61
|
||||||
|
taupe,#483c32
|
||||||
|
tea green,#ccfb5d
|
||||||
|
teal,#008080
|
||||||
|
thistle,#d2b9d3
|
||||||
|
tiffany blue,#81d8d0
|
||||||
|
tiger orange,#c88141
|
||||||
|
tron blue,#7dfdfe
|
||||||
|
tulip pink,#c25a7c
|
||||||
|
turquoise,#43c6db
|
||||||
|
tyrian purple,#c45aec
|
||||||
|
valentine red,#e55451
|
||||||
|
vampire gray,#565051
|
||||||
|
vanilla,#f3e5ab
|
||||||
|
velvet maroon,#7e354d
|
||||||
|
venom green,#728c00
|
||||||
|
viola purple,#7e587e
|
||||||
|
violet red,#f6358a
|
||||||
|
violet,#8d38c9
|
||||||
|
water,#ebf4fa
|
||||||
|
watermelon pink,#fc6c85
|
||||||
|
white,#ffffff
|
||||||
|
windows blue,#357ec7
|
||||||
|
wisteria purple,#c6aec7
|
||||||
|
wood,#966f33
|
||||||
|
yellow green,#52d017
|
||||||
|
yellow,#ffff00
|
||||||
|
zombie green,#54c571
|
||||||
|