33 Commits

Author SHA1 Message Date
d500c130ed Changelog update 2022-12-10 11:55:26 +01:00
b4d390cd9b Feheler beim buildtask der mp3-jar behoben 2022-12-10 11:55:19 +01:00
97ea610f34 Dokumentation 2022-12-10 11:14:38 +01:00
e2e1f24e3e Verison auf 0.0.34 erhöht 2022-12-10 11:11:26 +01:00
a09b956b48 Dokumentation
Ebene der Unterüberschrift auf h2 angepasst
2022-12-10 11:11:17 +01:00
c92a4517b3 Faker Klasse um Fake-Daten zu generieren 2022-12-10 11:07:54 +01:00
1260a38bb7 Imports bereinigt 2022-12-10 11:07:17 +01:00
20772da813 {@inheritDoc} Kommentare entfernt
Javadoc Kommentare, die nur {@inheritDoc} enthalten, sind redundant und wurden entfernt.
2022-12-10 11:06:51 +01:00
8898d6e8ee MKDocs Dokumentation erweitert 2022-12-10 11:05:09 +01:00
cb0ee9c842 Dokumentation 2022-12-10 11:04:48 +01:00
3cf7871591 Weitere image-Methoden ergänzt
Die neuen Methoden erlauben es, Bilder auch mit einer festen Größe auf die Zeichenebene zu zeichnen.
2022-12-09 18:01:52 +01:00
c7e1eb11ed maxInt Test eingefügt 2022-12-08 21:16:35 +01:00
9fc58b05b6 Dokumentation 2022-12-08 21:16:01 +01:00
8de3c41b9b Dokumentation 2022-12-08 16:09:13 +01:00
15e47ceaa8 Reichenfolge Parameter in addValue an PieChart angepasst 2022-12-08 16:08:59 +01:00
9f28786ab6 AudiListener interface angepasst
Die Listener Methoden haben nun sprechendere Namen.
Sound und Mixer akzeptieren nun auch AudioListener.
2022-12-08 16:08:35 +01:00
18b5c50016 Dokumentation 2022-12-08 12:47:59 +01:00
03945e029a Schnellstart Tutorial fertig 2022-12-08 10:16:22 +01:00
90e043e5f8 Gradle Tasks für source und javadoc jars 2022-12-08 10:16:07 +01:00
559459aef6 Dokumentation 2022-12-08 10:15:35 +01:00
8d0bd2bc99 Race condition beim Beenden behoben
Das Beenden der Zeichenmaschine und vor allem das Schließen des Zeichenfesnters wird im Swing Thread ausgeführt. Es konnte passieren, dass der Zeichenthread noch einen draw-Aufruf verarbeitete, während die Zeichenleinwand schon disposed wurde. Dann konnte eine NullPointerException auftreten.

Der Zeichenthread hat nun 500 ms Zeit, von alleine zu beenden, bevor die ZM vollständig beendet wird.
2022-12-08 10:14:00 +01:00
b76d533739 Erste Seiten mit mkdocs 2022-11-29 10:55:54 +01:00
807a13b725 buildfile cleanup 2022-11-29 10:55:22 +01:00
d4c5dbbb53 Gradle 7.4 -> 7.5 2022-11-29 10:55:10 +01:00
080db1f431 Einige Bugfixes und Verbesserungen und ganz viel Doku 2022-11-29 10:12:14 +01:00
47827683e8 Farbnamen werden nun in Colo-Objekte geparsed
`Color.parseString(String)` liest nun eine Datei mit Farbnamen und Hexcode Kombinationen ein. Wird der String in der Liste der Farbnamen gefunden, wird aus dem entsprechenden Hexcode ein `Color`-Objekt erzeugt.
2022-11-29 10:11:43 +01:00
ec30afd441 Fixed icon loading on windows 2022-11-28 09:26:45 +01:00
d3bdbdbffb Konstanten für Schriften und kleinere fixes 2022-11-28 09:11:37 +01:00
8cc7167d7e Formatierung und Doku 2022-11-28 09:11:13 +01:00
9e4271c304 Laden alternativer Schriften möglich 2022-11-28 09:10:59 +01:00
4f13f5177d Mausposition merken wenn pausiert 2022-11-28 09:10:42 +01:00
6321a7d421 render Method added 2022-11-28 09:10:31 +01:00
135af10729 Python files added 2022-11-28 09:09:58 +01:00
77 changed files with 10353 additions and 3604 deletions

8
.gitignore vendored
View File

@@ -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

View File

@@ -6,6 +6,14 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [Unreleased] ## [Unreleased]
## 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 ### Changed
- `FilledShape` und `StrokedShape` durch `Fillable` und `Strokeable` Interfaces ersetzt. - `FilledShape` und `StrokedShape` durch `Fillable` und `Strokeable` Interfaces ersetzt.
- `Shape` erweitert nun `BasisDrawable` als abstrakte Grundlage. - `Shape` erweitert nun `BasisDrawable` als abstrakte Grundlage.

View File

@@ -3,18 +3,13 @@ plugins {
id 'java-library' id 'java-library'
} }
/*properties {
zmVersion {
major = 0;
minor = 0;
rev = 32;
}
}
version '{$zmVersion.major}.{$zmVersion.minor}.{$zmVersion.rev}-SNAPSHOT'
*/
group 'schule.ngb' group 'schule.ngb'
version '0.0.32-SNAPSHOT' version '0.0.34-SNAPSHOT'
java {
withSourcesJar()
withJavadocJar()
}
compileJava { compileJava {
options.release = 11 options.release = 11
@@ -49,4 +44,5 @@ tasks.register('jarMP3SPI', Jar) {
from { from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
} }
with jar
} }

BIN
docs/assets/icon_128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/assets/icon_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
docs/assets/icon_512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

BIN
docs/assets/icon_64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View 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": {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 KiB

View 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": {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 KiB

View 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": {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

12
docs/assets/zmstyles.css Normal file
View 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
View File

@@ -0,0 +1,43 @@
<figure markdown>
![Zeichenmaschine.xyz](assets/icon_512.png){ 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`.

View File

@@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block tabs %}
{{ super() }}
{% endblock %}
{% block content %}{% endblock %}
{% block footer %}{% endblock %}

46
docs/installation.md Normal file
View 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
View 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
View 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>
![Shapes 2](assets/quickstart/shapes_2.png){ 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>
![Shapes 3](assets/quickstart/shapes_3.png){ 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>
![Shapes 4](assets/quickstart/shapes_4.1.png){ 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>
![Shapes 5](assets/quickstart/shapes_5.1.png){ 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>
![Kollision Maus mit Kreis](assets/quickstart/CircleMouseCollision.png){ 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>
![Whack-a-mole](assets/quickstart/shapes_5.3.gif){ 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>
![Whack-a-mole](assets/quickstart/Layers.png){ 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>
![Whack-a-mole](assets/quickstart/AblaufMoleStatic.png){ 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
![Whack-a-mole](assets/quickstart/AblaufMoleActive.png){ 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>
![Whack-a-mole](assets/quickstart/shapes_6.1.gif){ 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>
![Whack-a-mole](assets/quickstart/shapes_6.2.png){ 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).

View 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.

View File

@@ -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
View 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
View 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

View File

@@ -1,6 +1,9 @@
package schule.ngb.zm; package schule.ngb.zm;
import java.awt.*; import java.awt.Graphics2D;
import java.awt.MultipleGradientPaint;
import java.awt.Paint;
import java.awt.Stroke;
/** /**
* Basisimplementierung der {@link Strokeable} und {@link Fillable} APIs. * Basisimplementierung der {@link Strokeable} und {@link Fillable} APIs.
@@ -90,9 +93,6 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
// Implementierung Fillable Interface // Implementierung Fillable Interface
/**
* {@inheritDoc}
*/
@Override @Override
public void setFill( Paint fill ) { public void setFill( Paint fill ) {
if( fill == null ) { if( fill == null ) {
@@ -106,9 +106,6 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
} }
} }
/**
* {@inheritDoc}
*/
@Override @Override
public Paint getFill() { public Paint getFill() {
if( fill != null ) { if( fill != null ) {
@@ -120,42 +117,27 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
} }
} }
/**
* {@inheritDoc}
*/
@Override @Override
public boolean hasFillColor() { public boolean hasFillColor() {
return fillColor != null; return fillColor != null;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public boolean hasGradient() { public boolean hasGradient() {
return fill != null; return fill != null;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public Color getFillColor() { public Color getFillColor() {
return fillColor; return fillColor;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public void setFillColor( Color color ) { public void setFillColor( Color color ) {
fillColor = color; fillColor = color;
fill = null; fill = null;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public MultipleGradientPaint getGradient() { public MultipleGradientPaint getGradient() {
return fill; return fill;
@@ -164,17 +146,11 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
// Implementierung Strokeable Interface // Implementierung Strokeable Interface
/**
* {@inheritDoc}
*/
@Override @Override
public void setStroke( Stroke stroke ) { public void setStroke( Stroke stroke ) {
this.stroke = stroke; this.stroke = stroke;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public Stroke getStroke() { public Stroke getStroke() {
if( stroke == null ) { if( stroke == null ) {
@@ -183,50 +159,32 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
return stroke; return stroke;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public Color getStrokeColor() { public Color getStrokeColor() {
return strokeColor; return strokeColor;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public void setStrokeColor( Color color ) { public void setStrokeColor( Color color ) {
strokeColor = color; strokeColor = color;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public double getStrokeWeight() { public double getStrokeWeight() {
return strokeWeight; return strokeWeight;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public void setStrokeWeight( double weight ) { public void setStrokeWeight( double weight ) {
strokeWeight = weight; strokeWeight = weight;
this.stroke = null; this.stroke = null;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public Options.StrokeType getStrokeType() { public Options.StrokeType getStrokeType() {
return strokeType; return strokeType;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public void setStrokeType( Options.StrokeType type ) { public void setStrokeType( Options.StrokeType type ) {
strokeType = type; strokeType = type;

View File

@@ -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.
@@ -48,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.
*/ */
@@ -89,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.
*/ */
@@ -108,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.
*/ */
@@ -117,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.
*/ */
@@ -127,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.
@@ -140,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.
@@ -156,20 +190,20 @@ public class Color implements Paint {
} }
/** /**
* 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.getRGBA(), true); 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);
@@ -204,45 +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 ) {
return new Color(rgba, true); return new Color(rgba, true);
} }
/**
* 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("#") ) {
@@ -279,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 ) {
@@ -329,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);
} }
@@ -337,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,10 +578,11 @@ public class Color implements Paint {
/** /**
* 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}.
*/ */

View File

@@ -1,14 +1,17 @@
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.Validator;
import schule.ngb.zm.util.io.ImageLoader; import schule.ngb.zm.util.io.ImageLoader;
import schule.ngb.zm.util.Noise; import schule.ngb.zm.util.Noise;
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.Arrays;
import java.util.Collections;
import java.util.Random; import java.util.Random;
import java.util.function.DoubleUnaryOperator; import java.util.function.DoubleUnaryOperator;
@@ -65,7 +68,7 @@ public class Constants {
/** /**
* Patchversion der Zeichenmaschine. * Patchversion der Zeichenmaschine.
*/ */
public static final int APP_VERSION_REV = 31; public static final int APP_VERSION_REV = 32;
/** /**
* Version der Zeichenmaschine als Text-String. * Version der Zeichenmaschine als Text-String.
@@ -442,6 +445,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
@@ -768,6 +786,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.
* *
@@ -792,6 +834,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.
* *
@@ -1037,7 +1103,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)}.
@@ -1047,7 +1113,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)}.
@@ -1187,6 +1253,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.
*/ */
@@ -1323,9 +1395,9 @@ public class Constants {
* @param values Ein Array mit Werten, die zur Auswahl stehen. * @param values Ein Array mit Werten, die zur Auswahl stehen.
* @return Ein zufälliges Element aus dem Array. * @return Ein zufälliges Element aus dem Array.
*/ */
public static final int choice( int[] values ) { /*public static final int choice( int... values ) {
return values[random(0, values.length - 1)]; 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.
@@ -1333,9 +1405,9 @@ public class Constants {
* @param values Ein Array mit Werten, die zur Auswahl stehen. * @param values Ein Array mit Werten, die zur Auswahl stehen.
* @return Ein zufälliges Element aus dem Array. * @return Ein zufälliges Element aus dem Array.
*/ */
public static final double choice( double[] values ) { /*public static final double choice( double... values ) {
return values[random(0, values.length - 1)]; 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.
@@ -1344,7 +1416,7 @@ 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 ) { public static final <T> T choice( T... values ) {
return values[random(0, values.length - 1)]; return values[random(0, values.length - 1)];
} }
@@ -1387,7 +1459,12 @@ 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. * @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 ) { public static final double[] choice( double[] values, int n, boolean unique ) {
if( unique && values.length < n ) if( unique && values.length < n )
@@ -1416,8 +1493,13 @@ 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).
* @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, boolean unique ) { public static final <T> T[] choice( T[] values, int n, boolean unique ) {
if( unique && values.length < n ) if( unique && values.length < n )
@@ -1441,6 +1523,53 @@ public class Constants {
return 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);
}
/** /**
* Geteilte {@code Noise}-Instanz zur Erzeugung von Perlin-Noise. * Geteilte {@code Noise}-Instanz zur Erzeugung von Perlin-Noise.
*/ */
@@ -1607,38 +1736,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 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);
@@ -1647,38 +1831,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);
} }
@@ -1739,7 +1978,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.

View File

@@ -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,7 +88,11 @@ 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;
} }
/** /**
@@ -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 ) {
@@ -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());
}
} }

View File

@@ -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,25 +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,
/**
* 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, 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, UPDATING,
/**
* Die Zeichenmaschine führt gerade {@link Zeichenmaschine#draw()} aus.
*/
DRAWING, 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),
@@ -89,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;
} }
} }
}
} }

View File

@@ -15,7 +15,7 @@ import java.awt.Stroke;
* {@link Strokeable#setStrokeColor(Color, int)} Methode hat, dann sollte auch * {@link Strokeable#setStrokeColor(Color, int)} Methode hat, dann sollte auch
* eine {@link schule.ngb.zm.layers.TurtleLayer.Turtle} dieselbe Methode * eine {@link schule.ngb.zm.layers.TurtleLayer.Turtle} dieselbe Methode
* anbieten. Im Einzelfall kann es sinnvoll sein, weitere Methoden für * anbieten. Im Einzelfall kann es sinnvoll sein, weitere Methoden für
* Konturlinien zur erfügung zu stellen. Allerdings sollte davon nach * Konturlinien zur verfügung zu stellen. Allerdings sollte davon nach
* Möglichkeit zugunsten einer einheitlichen API abgesehen werden. * Möglichkeit zugunsten einer einheitlichen API abgesehen werden.
* <p> * <p>
* Das Äquivalent für Füllungen stellt {@link Fillable} dar. * Das Äquivalent für Füllungen stellt {@link Fillable} dar.

View File

@@ -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 );
} }

View File

@@ -422,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);

View File

@@ -9,9 +9,7 @@ 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.lang.reflect.Method;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
@@ -25,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());
@@ -33,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.
@@ -55,10 +70,10 @@ 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 es * Bevorzugte Abmessungen der Zeichenleinwand. Für das Zeichenfenster hat es
@@ -90,20 +105,56 @@ public class Zeichenfenster extends JFrame {
} }
}; };
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).getDefaultConfiguration());
this.displayDevice = displayDevice; this.displayDevice = displayDevice;
@@ -124,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);
@@ -150,19 +206,45 @@ public class Zeichenfenster extends JFrame {
// 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 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();
} }
@@ -179,6 +261,12 @@ 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 constrains on max/min frame/canvas size // TODO: (ngb) Put constrains on max/min frame/canvas size
if( fullscreen ) { if( fullscreen ) {
@@ -259,6 +347,12 @@ public class Zeichenfenster extends JFrame {
} }
} }
/**
* 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);

View File

@@ -1,6 +1,5 @@
package schule.ngb.zm; package schule.ngb.zm;
import schule.ngb.zm.layers.ColorLayer;
import schule.ngb.zm.util.Log; import schule.ngb.zm.util.Log;
import java.awt.Canvas; import java.awt.Canvas;
@@ -8,7 +7,10 @@ import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.image.BufferStrategy; import java.awt.image.BufferStrategy;
import java.util.*; import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/** /**
* Eine Leinwand ist die Hauptkomponente einer Zeichenmaschine. Sie besteht aus * Eine Leinwand ist die Hauptkomponente einer Zeichenmaschine. Sie besteht aus
@@ -22,13 +24,13 @@ import java.util.*;
*/ */
public class Zeichenleinwand extends Canvas { public class Zeichenleinwand extends Canvas {
// Lokales Lock für Rendervorgänge.
private final Object[] renderLock = new Object[0]; private final Object[] renderLock = new Object[0];
/** // Liste der hinzugefügten Ebenen.
* Liste der hinzugefügten Ebenen.
*/
private final List<Layer> layers; private final List<Layer> layers;
// Status der Zeichenleinwand.
private boolean rendering = false, suspended = false; private boolean rendering = false, suspended = false;
/** /**
@@ -80,6 +82,10 @@ public class Zeichenleinwand extends Canvas {
} }
} }
/**
* Setzt das Zeichnen der Leinwand fort, falls es zuvor mit
* {@link #suspendRendering()} ausgesetzt wurde.
*/
public void resumeRendering() { public void resumeRendering() {
suspended = false; suspended = false;
} }
@@ -201,6 +207,7 @@ public class Zeichenleinwand extends Canvas {
* @param <L> Typ der Ebenen, die abgefragt werden. * @param <L> Typ der Ebenen, die abgefragt werden.
* @return Eine Liste mit den vorhandenen Ebenen des abgefragten Typs. * @return Eine Liste mit den vorhandenen Ebenen des abgefragten Typs.
*/ */
@SuppressWarnings( "unused" )
public <L extends Layer> List<L> getLayers( Class<L> type ) { 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 ) { synchronized( layers ) {
@@ -213,10 +220,24 @@ public class Zeichenleinwand extends Canvas {
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 ) {
return layers.remove(pLayer); return layers.remove(pLayer);
} }
/**
* Entfernt alle angegebenen Ebenen von dieser Zeichenleinwand.
*
* @param removeLayers Die Ebenen, die entfernt werden sollen.
*/
@SuppressWarnings( "unused" )
public void removeLayers( Layer... removeLayers ) { public void removeLayers( Layer... removeLayers ) {
synchronized( layers ) { synchronized( layers ) {
for( Layer layer : removeLayers ) { for( Layer layer : removeLayers ) {
@@ -225,10 +246,21 @@ public class Zeichenleinwand extends Canvas {
} }
} }
/**
* Entfernt alle vorhandenen Ebenen von dieser Zeichenleinwand.
*/
@SuppressWarnings( "unused" )
public void clearLayers() { public void clearLayers() {
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 ) {
for( Layer layer : List.copyOf(layers) ) { for( Layer layer : List.copyOf(layers) ) {
@@ -261,9 +293,9 @@ 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();

View File

@@ -427,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();
@@ -670,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>
@@ -938,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
@@ -1013,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
*/ */
@@ -1091,6 +1100,9 @@ public class Zeichenmaschine extends Constants {
} }
if( isPaused() || isTerminated() ) { if( isPaused() || isTerminated() ) {
if( MouseEvent.class.isInstance(evt) ) {
saveMousePosition((MouseEvent)evt);
}
dispatchEvents(); dispatchEvents();
} }
} }
@@ -1435,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();
@@ -1445,6 +1461,7 @@ public class Zeichenmaschine extends Constants {
quit(); quit();
} }
} }
}
} }

View File

@@ -57,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);
@@ -67,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.
*/ */
@@ -86,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;
@@ -116,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(
@@ -125,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(
@@ -138,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);

View File

@@ -87,9 +87,6 @@ public class DrawableLayer extends Layer {
this.clearBeforeDraw = pClearBeforeDraw; this.clearBeforeDraw = pClearBeforeDraw;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public void draw( Graphics2D graphics ) { public void draw( Graphics2D graphics ) {
if( clearBeforeDraw ) { if( clearBeforeDraw ) {

View File

@@ -1,10 +1,7 @@
package schule.ngb.zm.layers; package schule.ngb.zm.layers;
import schule.ngb.zm.Color; import schule.ngb.zm.Color;
import schule.ngb.zm.Layer; import schule.ngb.zm.*;
import schule.ngb.zm.Options;
import schule.ngb.zm.Fillable;
import schule.ngb.zm.Strokeable;
import schule.ngb.zm.shapes.Text; import schule.ngb.zm.shapes.Text;
import schule.ngb.zm.util.io.ImageLoader; import schule.ngb.zm.util.io.ImageLoader;
@@ -66,7 +63,7 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
private final Stack<schule.ngb.zm.shapes.Text> styleStack; private final Stack<schule.ngb.zm.shapes.Text> styleStack;
/** /**
* Erstellt eine Ebene in der Standardgröße. * Erstellt eine Zeichenebene in der Standardgröße.
*/ */
public DrawingLayer() { public DrawingLayer() {
super(); super();
@@ -79,7 +76,7 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
} }
/** /**
* Erstellt eine Ebene mit der angegebenen Größe. * Erstellt eine Zeichenebene mit der angegebenen Größe.
* *
* @param width Die Breite der Ebene. * @param width Die Breite der Ebene.
* @param height Die Höhe der Ebene. * @param height Die Höhe der Ebene.
@@ -254,12 +251,18 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
} }
/** /**
* Entfernt den Farbverlauf von der Form. * Entfernt einen Farbverlauf von der Füllung.
*/ */
public void noGradient() { public void noGradient() {
shapeDelegate.noGradient(); shapeDelegate.noGradient();
} }
/**
* Setzt den Linienstil für Konturlinien direkt auf das angegebene
* {@code Stroke}-Objekt.
*
* @param stroke Ein {@code Stroke}-Objekt.
*/
@Override @Override
public void setStroke( Stroke stroke ) { public void setStroke( Stroke stroke ) {
shapeDelegate.setStroke(stroke); shapeDelegate.setStroke(stroke);
@@ -417,59 +420,171 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
return shapeDelegate.getStroke(); return shapeDelegate.getStroke();
} }
/**
* Setzt den Standard-ANker für die Zeichenebene auf die angegebene
* Richtung.
* <p>
* Zu Beginn ist der Standardanker immer auf
* {@link Options.Direction#CENTER CENTER} gesetzt. Alle Formen werden von
* der Mitte aus gezeichnet. Wir der Anker mit {@code setAnchor(NORTHWEST)}
* beispielsweise auf {@link Options.Direction#NORTHWEST NORTHWEST} gesetzt,
* werden alle Formen von der linken oberen Ecke aus gezeichnet.
*
* @param anchor Der neue Anker.
*/
public void setAnchor( Options.Direction anchor ) { public void setAnchor( Options.Direction anchor ) {
shapeDelegate.setAnchor(anchor); shapeDelegate.setAnchor(anchor);
} }
/**
* Übermalt die komplette Ebene mit einem 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 clear( int gray ) { public void clear( int gray ) {
clear(gray, gray, gray, 255); clear(gray, gray, gray, 255);
} }
/**
* Übermalt die komplette Ebene mit einem 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 clear( int gray, int alpha ) { public void clear( int gray, int alpha ) {
clear(gray, gray, gray, alpha); clear(gray, gray, gray, alpha);
} }
/**
* Übermalt die komplette Ebene mit der 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 clear( int red, int green, int blue ) { public void clear( int red, int green, int blue ) {
clear(red, green, blue, 255); clear(red, green, blue, 255);
} }
/**
* Übermalt die komplette Ebene mit der 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 clear( int red, int green, int blue, int alpha ) { public void clear( int red, int green, int blue, int alpha ) {
clear(new schule.ngb.zm.Color(red, green, blue, alpha)); clear(new schule.ngb.zm.Color(red, green, blue, alpha));
} }
public void clear( schule.ngb.zm.Color pColor ) { /**
* Übermalt die komplette Ebene mit der angegebenen Farbe.
*
* @param color Die neue Füllfarbe oder {@code null}.
* @see Color
*/
public void clear( schule.ngb.zm.Color color ) {
/*graphics.setBackground(pColor); /*graphics.setBackground(pColor);
graphics.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());*/ graphics.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());*/
java.awt.Color currentColor = drawing.getColor(); java.awt.Color currentColor = drawing.getColor();
pushMatrix(); pushMatrix();
resetMatrix(); resetMatrix();
drawing.setColor(pColor.getJavaColor()); drawing.setColor(color.getJavaColor());
drawing.fillRect(0, 0, buffer.getWidth(), buffer.getHeight()); drawing.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());
drawing.setColor(currentColor); drawing.setColor(currentColor);
popMatrix(); popMatrix();
} }
/**
* Zeichnet eine gerade Line zwischen den angegebenen Koordinaten.
*
* @param x1 Die x-Koordinate des Startpunktes.
* @param y1 Die y-Koordinate des Startpunktes.
* @param x2 Die x-Koordinate des Endpunktes.
* @param y2 Die y-Koordinate des Endpunktes.
*/
public void line( double x1, double y1, double x2, double y2 ) { public void line( double x1, double y1, double x2, double y2 ) {
line.setLine(x1, y1, x2, y2); line.setLine(x1, y1, x2, y2);
drawShape(line); drawShape(line);
} }
/**
* Färbt den Pixel an der angegebenen Koordinate ein.
*
* @param x Die x-Koordinate.
* @param y Die y-Koordinate.
*/
public void pixel( double x, double y ) { public void pixel( double x, double y ) {
buffer.setRGB((int) x, (int) y, shapeDelegate.getFontColor().getRGBA()); buffer.setRGB((int) x, (int) y, shapeDelegate.getFontColor().getRGBA());
} }
/**
* Zeichnet ein Quadrat an den angegebenen Koordinaten mit der angegebenen
* Kantenlänge in die Zeichenebene.
* <p>
* Als Ankerpunkt wird der Standardanker verwendet.
*
* @param x Die x-Koordinate des Ankerpunktes.
* @param y Die y-Koordinate des Ankerpunktes.
* @param w Die Kantenlänge des Quadrats.
* @see #square(double, double, double, Options.Direction)
*/
public void square( double x, double y, double w ) { public void square( double x, double y, double w ) {
rect(x, y, w, w, shapeDelegate.getAnchor()); rect(x, y, w, w, shapeDelegate.getAnchor());
} }
/**
* Zeichnet ein Quadrat an den angegebenen Koordinaten mit der angegebenen
* Kantenlänge und dem angegebenen Ankerpunkt in die Zeichenebene.
*
* @param x Die x-Koordinate des Ankerpunktes.
* @param y Die y-Koordinate des Ankerpunktes.
* @param w Die Kantenlänge des Quadrats.
* @see #square(double, double, double, Options.Direction)
*/
public void square( double x, double y, double w, Options.Direction anchor ) { public void square( double x, double y, double w, Options.Direction anchor ) {
rect(x, y, w, w, anchor); rect(x, y, w, w, anchor);
} }
/**
* Zeichnet ein Rechteck an den angegebenen Koordinaten mit der angegebenen
* Breite und Höhe in die Zeichenebene.
* <p>
* Als Ankerpunkt wird der Standardanker verwendet.
*
* @param x x-Koordinate des Rechtecks.
* @param y y-Koordinate des Rechtecks.
* @param w Breite des Rechtecks.
* @param h Höhe des Rechtecks.
*/
public void rect( double x, double y, double w, double h ) { public void rect( double x, double y, double w, double h ) {
rect(x, y, w, h, shapeDelegate.getAnchor()); rect(x, y, w, h, shapeDelegate.getAnchor());
} }
/**
* Zeichnet ein Rechteck an den angegebenen Koordinaten mit der angegebenen
* Breite und Höhe und dem angegebenen Ankerpunkt in die Zeichenebene.
*
* @param x x-Koordinate des Rechtecks.
* @param y y-Koordinate des Rechtecks.
* @param w Breite des Rechtecks.
* @param h Höhe des Rechtecks.
* @param anchor Ankerpunkt de Rechtecks.
*/
public void rect( double x, double y, double w, double h, Options.Direction anchor ) { public void rect( double x, double y, double w, double h, Options.Direction anchor ) {
Point2D.Double anchorPoint = this.getOriginPoint(x, y, w, h, anchor); Point2D.Double anchorPoint = this.getOriginPoint(x, y, w, h, anchor);
rect.setRect(anchorPoint.getX(), anchorPoint.getY(), w, h); rect.setRect(anchorPoint.getX(), anchorPoint.getY(), w, h);
@@ -477,22 +592,103 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
drawShape(rect); drawShape(rect);
} }
/**
* Zeichnet ein Rechteck mit abgerundeten Ecken an den angegebenen
* Koordinaten mit der angegebenen Breite und Höhe in die Zeichenebene.
* <p>
* Jede Ecke wird als Viertelkreis eines Kreises mit dem Radius
* {@code radius} gezeichnet.
*
* @param x x-Koordinate des Rechtecks.
* @param y y-Koordinate des Rechtecks.
* @param w Breite des Rechtecks.
* @param h Höhe des Rechtecks.
* @param radius Radius der Eckenkreise.
*/
public void roundedRect( double x, double y, double w, double h, double radius ) {
roundedRect(x, y, w, h, radius, shapeDelegate.getAnchor());
}
/**
* Zeichnet ein Rechteck mit abgerundeten Ecken an den angegebenen
* Koordinaten mit der angegebenen Breite und Höhe und dem angegebenen
* Ankerpunkt in die Zeichenebene.
* <p>
* Jede Ecke wird als Viertelkreis eines Kreises mit dem Radius
* {@code radius} gezeichnet.
*
* @param x x-Koordinate des Rechtecks.
* @param y y-Koordinate des Rechtecks.
* @param w Breite des Rechtecks.
* @param h Höhe des Rechtecks.
* @param radius Radius der Eckenkreise.
* @param anchor Ankerpunkt de Rechtecks.
*/
public void roundedRect( double x, double y, double w, double h, double radius, Options.Direction anchor ) {
Point2D.Double anchorPoint = this.getOriginPoint(x, y, w, h, anchor);
Shape rect = new RoundRectangle2D.Double(anchorPoint.getX(), anchorPoint.getY(), w, h, radius, radius);
fillShape(rect);
drawShape(rect);
}
/**
* Zeichnet einen Punkt in die Zeichenebene.
* <p>
* Im Gegensatz zu einem {@link #pixel(double, double) Pixel}, besitzt ein
* Punkt einen Radius und nimmt mehr als ein Pixel auf der Ebene ein.
*
* @param x x-Koordinate des Punktes.
* @param y y-Koordinate des Punktes.
*/
public void point( double x, double y ) { public void point( double x, double y ) {
ellipse(x - 1, y - 1, 2, 2, shapeDelegate.getAnchor()); ellipse(x - 1, y - 1, 2, 2, shapeDelegate.getAnchor());
} }
/**
* Zeichnet einen Kreis an den angegebenen Koordinaten mit dem angegebenen
* Radius.
*
* @param x
* @param y
* @param r
*/
public void circle( double x, double y, double r ) { public void circle( double x, double y, double r ) {
ellipse(x, y, r + r, r + r, shapeDelegate.getAnchor()); ellipse(x, y, r + r, r + r, shapeDelegate.getAnchor());
} }
/**
* Zeichnet einen Kreis an den angegebenen Koordinaten mit dem angegebenen
* Radius und dem angegebenen Ankerpunkt.
*
* @param x
* @param y
* @param r
*/
public void circle( double x, double y, double r, Options.Direction anchor ) { public void circle( double x, double y, double r, Options.Direction anchor ) {
ellipse(x, y, r + r, r + r, anchor); ellipse(x, y, r + r, r + r, anchor);
} }
/**
* Zeichnet eine Ellipse in die Zeichenebene.
*
* @param x
* @param y
* @param w
* @param h
*/
public void ellipse( double x, double y, double w, double h ) { public void ellipse( double x, double y, double w, double h ) {
ellipse(x, y, w, h, shapeDelegate.getAnchor()); ellipse(x, y, w, h, shapeDelegate.getAnchor());
} }
/**
* Zeichnet eine Ellipse in die Zeichenebene.
*
* @param x
* @param y
* @param w
* @param h
* @param anchor
*/
public void ellipse( double x, double y, double w, double h, Options.Direction anchor ) { public void ellipse( double x, double y, double w, double h, Options.Direction anchor ) {
Point2D.Double anchorPoint = getOriginPoint(x, y, w, h, anchor); Point2D.Double anchorPoint = getOriginPoint(x, y, w, h, anchor);
ellipse.setFrame(anchorPoint.x, anchorPoint.y, w, h); ellipse.setFrame(anchorPoint.x, anchorPoint.y, w, h);
@@ -500,10 +696,35 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
drawShape(ellipse); drawShape(ellipse);
} }
/**
* Zeichnet einen Kreisbogen in die Zeichenebene.
* <p>
* Der Kreisbogen liegt auf einem Kreis mit dem angegebenen Radius, beginnt
* beim angegebenen Winkel und läuft bis zum zweiten Winkel.
*
* @param x
* @param y
* @param r
* @param angle1
* @param angle2
*/
public void arc( double x, double y, double r, double angle1, double angle2 ) { public void arc( double x, double y, double r, double angle1, double angle2 ) {
arc(x, y, r + r, r + r, angle1, angle2); arc(x, y, r + r, r + r, angle1, angle2);
} }
/**
* Zeichnet einen Ellipsenbogen in die Zeichenebene.
* <p>
* Der Ellipsenbogen liegt auf einer Ellipse mit der angegebenen Breite und
* Höhe, beginnt beim angegebenen Winkel und läuft bis zum zweiten Winkel.
*
* @param x
* @param y
* @param w
* @param h
* @param angle1
* @param angle2
*/
public void arc( double x, double y, double w, double h, double angle1, double angle2 ) { public void arc( double x, double y, double w, double h, double angle1, double angle2 ) {
while( angle2 < angle1 ) angle2 += 360.0; while( angle2 < angle1 ) angle2 += 360.0;
@@ -519,6 +740,15 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
drawShape(arc); drawShape(arc);
} }
/**
* Zeichnet einen Kreisausschnitt in die Zeichenebene.
*
* @param x
* @param y
* @param r
* @param angle1
* @param angle2
*/
public void pie( double x, double y, double r, double angle1, double angle2 ) { public void pie( double x, double y, double r, double angle1, double angle2 ) {
while( angle2 < angle1 ) angle2 += 360.0; while( angle2 < angle1 ) angle2 += 360.0;
@@ -536,16 +766,50 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
drawShape(arc); drawShape(arc);
} }
/**
* Zeichnet eine quadratische Bézierkurve mit den angegebenen Start- und
* Endkoordinaten, sowie dem angegebenen Kontrollpunkt.
*
* @param x1
* @param y1
* @param x2
* @param y2
* @param x3
* @param y3
*/
public void curve( double x1, double y1, double x2, double y2, double x3, double y3 ) { public void curve( double x1, double y1, double x2, double y2, double x3, double y3 ) {
QuadCurve2D curve = new QuadCurve2D.Double(x1, y1, x2, y2, x3, y3); QuadCurve2D curve = new QuadCurve2D.Double(x1, y1, x2, y2, x3, y3);
drawShape(curve); drawShape(curve);
} }
/**
* Zeichnet eine kubische Bézierkurve mit den angegebenen Start- und
* Endkoordinaten, sowie den angegebenen Kontrollpunkten.
*
* @param x1
* @param y1
* @param x2
* @param y2
* @param x3
* @param y3
* @param x4
* @param y4
*/
public void curve( double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4 ) { public void curve( double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4 ) {
CubicCurve2D curve = new CubicCurve2D.Double(x1, y1, x2, y2, x3, y3, x4, y4); CubicCurve2D curve = new CubicCurve2D.Double(x1, y1, x2, y2, x3, y3, x4, y4);
drawShape(curve); drawShape(curve);
} }
/**
* Zeichnet ein Dreieck mit den angegebenen Eckpunkten in die Zeichenebene.
*
* @param x1
* @param y1
* @param x2
* @param y2
* @param x3
* @param y3
*/
public void triangle( double x1, double y1, double x2, double y2, double x3, double y3 ) { public void triangle( double x1, double y1, double x2, double y2, double x3, double y3 ) {
Path2D path = new Path2D.Double(); Path2D path = new Path2D.Double();
path.moveTo(x1, y1); path.moveTo(x1, y1);
@@ -557,6 +821,14 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
drawShape(path); drawShape(path);
} }
/**
* Zeichnet einen Rhombus in die Zeichenebene.
*
* @param x
* @param y
* @param width
* @param height
*/
public void rhombus( double x, double y, double width, double height ) { public void rhombus( double x, double y, double width, double height ) {
rhombus(x, y, width, height, shapeDelegate.getAnchor()); rhombus(x, y, width, height, shapeDelegate.getAnchor());
} }
@@ -624,11 +896,24 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
} }
} }
/**
* Startet eine neue Freihand-Form.
*/
public void beginShape() { public void beginShape() {
path.reset(); path.reset();
pathStarted = false; pathStarted = false;
} }
/**
* Fügt einer zuvor {@link #beginShape() begonnenen} Freihand-Form eine
* Linie zu den angegebenen Koordinaten hinzu.
* <p>
* Wurde seit dem Start der Form noch keine Linie hinzugefügt, wird zunächst
* nur die angegebene Koordinate als Startpunkt der Freihand-Form gesetzt.
*
* @param x Die x-Koordinate.
* @param y Die y-Koordinate.
*/
public void lineTo( double x, double y ) { public void lineTo( double x, double y ) {
if( !pathStarted ) { if( !pathStarted ) {
path.moveTo(x, y); path.moveTo(x, y);
@@ -638,6 +923,10 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
} }
} }
/**
* Beendet eine zuvor {@link #beginShape() begonnene} Freihand-Form und
* zeichent sie auf die Zeichenebene.
*/
public void endShape() { public void endShape() {
path.closePath(); path.closePath();
path.trimToSize(); path.trimToSize();
@@ -647,72 +936,339 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
drawShape(path); drawShape(path);
} }
/**
* Zeichnet das Bild von der angegebenen Quelle an der angegebenen Position
* auf die Zeichenebene.
* <p>
* Die Bildquelle wird mithilfe von {@link ImageLoader#loadImage(String)}
* geladen. Schlägt dies fehl, wird nichts gezeichnet.
*
* @param imageSource Die Bildquelle.
* @param x x-Koordinate des Ankerpunktes.
* @param y y-Koordinate des Ankerpunktes.
* @see ImageLoader#loadImage(String)
*/
public void image( String imageSource, double x, double y ) { public void image( String imageSource, double x, double y ) {
image(ImageLoader.loadImage(imageSource), x, y, 1.0, shapeDelegate.getAnchor()); image(ImageLoader.loadImage(imageSource), x, y, 1.0, shapeDelegate.getAnchor());
} }
/**
* Zeichnet das Bild von der angegebenen Quelle an der angegebenen Position
* auf die Zeichenebene.
* <p>
* Die Bildquelle wird mithilfe von {@link ImageLoader#loadImage(String)}
* geladen. Schlägt dies fehl, wird nichts gezeichnet.
*
* @param imageSource Die Bildquelle.
* @param x x-Koordinate des Ankerpunktes.
* @param y y-Koordinate des Ankerpunktes.
* @param anchor Der Ankerpunkt.
* @see ImageLoader#loadImage(String)
*/
public void image( String imageSource, double x, double y, Options.Direction anchor ) { public void image( String imageSource, double x, double y, Options.Direction anchor ) {
image(ImageLoader.loadImage(imageSource), x, y, 1.0, anchor); image(ImageLoader.loadImage(imageSource), x, y, 1.0, anchor);
} }
/**
* Zeichnet das Bild von der angegebenen Quelle an den angegebenen
* Koordinaten auf die Zeichenebene. Das Bild wird um den angegebenen Faktor
* skaliert.
* <p>
* Siehe {@link #image(Image, double, double, double, Options.Direction)}
* für mehr Details.
*
* @param imageSource Die Bildquelle.
* @param x x-Koordinate des Ankerpunktes.
* @param y y-Koordinate des Ankerpunktes.
* @param scale Der Skalierungsfaktor des Bildes.
* @see ImageLoader#loadImage(String)
*/
public void image( String imageSource, double x, double y, double scale ) { public void image( String imageSource, double x, double y, double scale ) {
image(ImageLoader.loadImage(imageSource), x, y, scale, shapeDelegate.getAnchor()); image(ImageLoader.loadImage(imageSource), x, y, scale, shapeDelegate.getAnchor());
} }
/**
* Zeichnet das Bild von der angegebenen Quelle an den angegebenen
* Koordinaten auf die Zeichenebene. Das Bild wird um den angegebenen Faktor
* skaliert und der angegebene Ankerpunkt verwendet.
* <p>
* Siehe {@link #image(Image, double, double, double, Options.Direction)}
* für mehr Details.
*
* @param imageSource Die Bildquelle.
* @param x x-Koordinate des Ankerpunktes.
* @param y y-Koordinate des Ankerpunktes.
* @param scale Der Skalierungsfaktor des Bildes.
* @param anchor Der Ankerpunkt.
* @see ImageLoader#loadImage(String)
*/
public void image( String imageSource, double x, double y, double scale, Options.Direction anchor ) { public void image( String imageSource, double x, double y, double scale, Options.Direction anchor ) {
image(ImageLoader.loadImage(imageSource), x, y, scale, anchor); image(ImageLoader.loadImage(imageSource), x, y, scale, anchor);
} }
/**
* Zeichnet das angegebene Bild an den angegebenen Koordinaten auf die
* Zeichenebene.
*
* @param image Das vorher geladene Bild.
* @param x x-Koordinate des Ankerpunktes.
* @param y y-Koordinate des Ankerpunktes.
*/
public void image( Image image, double x, double y ) { public void image( Image image, double x, double y ) {
image(image, x, y, 1.0, shapeDelegate.getAnchor()); image(image, x, y, 1.0, shapeDelegate.getAnchor());
} }
/**
* Zeichnet das angegebene Bild an den angegebenen Koordinaten auf die
* Zeichenebene. Das Bild wird um den angegebenen Faktor skaliert.
* <p>
* Siehe {@link #image(Image, double, double, double, Options.Direction)}
* für mehr Details.
*
* @param image Das vorher geladene Bild.
* @param x x-Koordinate des Ankerpunktes.
* @param y y-Koordinate des Ankerpunktes.
* @param scale Der Skalierungsfaktor des Bildes.
*/
public void image( Image image, double x, double y, double scale ) { public void image( Image image, double x, double y, double scale ) {
image(image, x, y, scale, shapeDelegate.getAnchor()); image(image, x, y, scale, shapeDelegate.getAnchor());
} }
/**
* Zeichnet das angegebene Bild an den angegebenen Koordinaten auf die
* Zeichenebene. Das Bild wird um den angegebenen Faktor skaliert und der
* angegebene Ankerpunkt verwendet.
* <p>
* Bei einem Faktor größer 0 wird das Bild vergrößert, bei einem Faktor
* kleiner 0 verkleinert. Bei negativen Werten wird das Bild entlang der x-
* bzw. y-Achse gespiegelt.
* <p>
* Das Seitenverhältnis wird immer beibehalten.
* <p>
* Soll das Bild innerhalb eines vorgegebenen Rechtecks liegen, sollte
* {@link #image(Image, double, double, double, double, Options.Direction)}
* verwendet werden.
*
* @param image Das vorher geladene Bild.
* @param x x-Koordinate des Ankerpunktes.
* @param y y-Koordinate des Ankerpunktes.
* @param scale Der Skalierungsfaktor des Bildes.
* @param anchor Der Ankerpunkt.
*/
public void image( Image image, double x, double y, double scale, Options.Direction anchor ) { public void image( Image image, double x, double y, double scale, Options.Direction anchor ) {
if( image != null ) { /*if( image != null ) {
double neww = image.getWidth(null) * scale; double neww = image.getWidth(null) * scale;
double newh = image.getHeight(null) * scale; double newh = image.getHeight(null) * scale;
Point2D.Double anchorPoint = getOriginPoint(x, y, neww, newh, anchor); Point2D.Double anchorPoint = getOriginPoint(x, y, neww, newh, anchor);
drawing.drawImage(image, (int) anchorPoint.x, (int) anchorPoint.y, (int) neww, (int) newh, null); drawing.drawImage(image, (int) anchorPoint.x, (int) anchorPoint.y, (int) neww, (int) newh, null);
}*/
double neww = image.getWidth(null) * scale;
double newh = image.getHeight(null) * scale;
image(image, x, y, neww, newh, anchor);
}
/**
* Zeichnet das Bild von der angegebenen Quelle an den angegebenen
* Koordinaten in der angegebenen Größe auf die Zeichenebene.
* <p>
* Siehe
* {@link #image(Image, double, double, double, double, Options.Direction)}
* für mehr Details.
*
* @param imageSource Die Bildquelle.
* @param x x-Koordinate des Ankerpunktes.
* @param y y-Koordinate des Ankerpunktes.
* @param width Breite des Bildes auf der Zeichenebene oder 0.
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
* @see ImageLoader#loadImage(String)
*/
public void image( String imageSource, double x, double y, double width, double height ) {
image(ImageLoader.loadImage(imageSource), x, y, width, height, shapeDelegate.getAnchor());
}
/**
* Zeichnet das Bild von der angegebenen Quelle an den angegebenen
* Koordinaten in der angegebenen Größe auf die Zeichenebene. Es wird der
* angegebene Ankerpunkt verwendet.
* <p>
* Siehe
* {@link #image(Image, double, double, double, double, Options.Direction)}
* für mehr Details.
*
* @param imageSource Die Bildquelle.
* @param x x-Koordinate des Ankerpunktes.
* @param y y-Koordinate des Ankerpunktes.
* @param width Breite des Bildes auf der Zeichenebene oder 0.
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
* @param anchor Der Ankerpunkt.
* @see ImageLoader#loadImage(String)
*/
public void image( String imageSource, double x, double y, double width, double height, Options.Direction anchor ) {
image(ImageLoader.loadImage(imageSource), x, y, width, height, anchor);
}
/**
* Zeichnet das angegebene Bild an den angegebenen Koordinaten in der
* angegebenen Größe auf die Zeichenebene.
* <p>
* Siehe
* {@link #image(Image, double, double, double, double, Options.Direction)}
* für mehr Details.
*
* @param image Ein Bild-Objekt.
* @param x x-Koordinate des Ankerpunktes.
* @param y y-Koordinate des Ankerpunktes.
* @param width Breite des Bildes auf der Zeichenebene oder 0.
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
*/
public void image( Image image, double x, double y, double width, double height ) {
image(image, x, y, width, height, shapeDelegate.getAnchor());
}
/**
* Zeichnet das angegebene Bild an den angegebenen Koordinaten in der
* angegebenen Größe auf die Zeichenebene. Der angegebene Ankerpunkt wird
* verwendet.
* <p>
* Das Bild wird innerhalb eines Rechtecks mit der angegebenen Breite und
* Höhe gezeichnet. Dabei wird das Abbild verzerrt, wenn beim Aufruf nicht
* auf ein passendes Seitenverhältnis der Werte zueinander geachtet wird.
* <p>
* Um das Bild auf eine bestimmte Breite oder Höhe festzulegen und die
* andere Größe passend zu skalieren, kann einer der Parameter auf 0 gesetzt
* werden.
* <p>
* Soll die Bildgröße unter Beachtung der Abmessungen um einen Faktor
* verändert werden, sollte
* {@link #image(Image, double, double, double, Options.Direction)}
* verwendet werden.
*
* @param image Ein Bild-Objekt.
* @param x x-Koordinate des Ankerpunktes.
* @param y y-Koordinate des Ankerpunktes.
* @param width Breite des Bildes auf der Zeichenebene oder 0.
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
* @param anchor Der Ankerpunkt.
*/
public void image( Image image, double x, double y, double width, double height, Options.Direction anchor ) {
// TODO: Use Validator or at least LOG a message if image == null?
if( image != null ) {
if( width == 0 ) {
width = (height / image.getHeight(null)) * image.getWidth(null);
} else if( height == 0 ) {
height = (width / image.getWidth(null)) * image.getHeight(null);
}
Point2D.Double anchorPoint = getOriginPoint(x, y, width, height, anchor);
drawing.drawImage(image, (int) anchorPoint.x, (int) anchorPoint.y, (int) width, (int) height, null);
} }
} }
/**
* @return Die aktuell verwendete Schriftart.
*/
public Font getFont() { public Font getFont() {
return shapeDelegate.getFont(); return shapeDelegate.getFont();
} }
/**
* Setzt die verwendete Schriftart für Texte direkt auf das angegebene
* {@code Font}-Objekt.
*
* @param newFont Ein {@code Font}-Objekt.
*/
public void setFont( Font newFont ) { public void setFont( Font newFont ) {
shapeDelegate.setFont(newFont); shapeDelegate.setFont(newFont);
// fontMetrics = drawing.getFontMetrics(); // fontMetrics = drawing.getFontMetrics();
} }
/**
* Setzt die verwendete Schriftart für Texte auf eine Schriftart mit dem
* angegebenen Namen.
* <p>
* Kann keine Schriftart mit einem passenden Namen geladen werden, wird die
* Schriftart nicht geändert.
*
* @param fontName Der Name der Schriftart.
* @see schule.ngb.zm.util.io.FontLoader#loadFont(String)
*/
public void setFont( String fontName ) { public void setFont( String fontName ) {
shapeDelegate.setFont(fontName); shapeDelegate.setFont(fontName);
} }
/**
* Setzt die verwendete Schriftart für Texte auf eine Schriftart mit dem
* angegebenen Namen und der angegebenen Schriftgröße.
* <p>
* Kann keine Schriftart mit einem passenden Namen geladen werden, wird die
* Schriftart nicht geändert.
*
* @param fontName Der Name der Schriftart.
* @param size Die Schriftgröße.
* @see schule.ngb.zm.util.io.FontLoader#loadFont(String)
*/
public void setFont( String fontName, int size ) { public void setFont( String fontName, int size ) {
shapeDelegate.setFont(fontName, size); shapeDelegate.setFont(fontName, size);
} }
/**
* Setzt die verwendete Schriftart für Texte auf eine Schriftart mit dem
* angegebenen Namen, der angegebenen Schriftgröße und dem angegebenen
* Schriftstil.
* <p>
* Der Schriftstil kann über die Stil-Konstanten
* {@link schule.ngb.zm.Constants#PLAIN PLAIN},
* {@link schule.ngb.zm.Constants#BOLD BOLD} und
* {@link schule.ngb.zm.Constants#ITALIC ITALIC} gesetzt werden.
* <p>
* Kann keine Schriftart mit einem passenden Namen geladen werden, wird die
* Schriftart nicht geändert.
*
* @param fontName Der Name der Schriftart.
* @param size Die Schriftgröße.
* @param style Der Schriftstil.
* @see schule.ngb.zm.util.io.FontLoader#loadFont(String)
*/
public void setFont( String fontName, int size, int style ) { public void setFont( String fontName, int size, int style ) {
shapeDelegate.setFont(fontName, size, style); shapeDelegate.setFont(fontName, size, style);
} }
/**
* @return Die aktuelle Schriftgröße für Texte.
*/
public double getFontSize() { public double getFontSize() {
return shapeDelegate.getFontSize(); return shapeDelegate.getFontSize();
} }
/**
* Setzt die Schriftgörße für Texte auf den angegebenen Wert.
*
* @param size Die neue Schriftgröße.
*/
public void setFontSize( double size ) { public void setFontSize( double size ) {
shapeDelegate.setFontSize(size); shapeDelegate.setFontSize(size);
} }
/**
* Schreibt einen Text an der angegebenen Position auf die Zeichenebene.
*
* @param text Der zu schreibende Text.
* @param x Die x-Koordinate des Ankerpunktes.
* @param y Die y-Koordinate des Ankerpunktes.
*/
public void text( String text, double x, double y ) { public void text( String text, double x, double y ) {
text(text, x, y, shapeDelegate.getAnchor()); text(text, x, y, shapeDelegate.getAnchor());
} }
/**
* Schreibt einen Text an der angegebenen Position auf die Zeichenebene und
* nutzt den angegebenen Ankerpunkt.
*
* @param text Der zu schreibende Text.
* @param x Die x-Koordinate des Ankerpunktes.
* @param y Die y-Koordinate des Ankerpunktes.
* @param anchor Position des Ankerpunktes.
*/
public void text( String text, double x, double y, Options.Direction anchor ) { public void text( String text, double x, double y, Options.Direction anchor ) {
drawing.setFont(shapeDelegate.getFont()); drawing.setFont(shapeDelegate.getFont());
FontMetrics fm = drawing.getFontMetrics(); FontMetrics fm = drawing.getFontMetrics();
@@ -729,7 +1285,7 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* Koordinaten {@code (300, 300)} und den Abmessungen {@code (100, 50)} * Koordinaten {@code (300, 300)} und den Abmessungen {@code (100, 50)}
* würde für den Anker {@link Options.Direction#NORTHWEST} also den Ursprung * würde für den Anker {@link Options.Direction#NORTHWEST} also den Ursprung
* {@code (300, 300)} haben. Für den Anker {@link Options.Direction#CENTER} * {@code (300, 300)} haben. Für den Anker {@link Options.Direction#CENTER}
* wäre der Ursprung zu den Koordinaten um die hälfte der Abmessungen nach * wäre der Ursprung zu den Koordinaten um die Hälfte der Abmessungen nach
* links und oben verschoben bei {@code (250, 275)}. Beim Zeichnen liegen * links und oben verschoben bei {@code (250, 275)}. Beim Zeichnen liegen
* die Koordinaten {@code (300, 300)} dann in der Mitte der Form. * die Koordinaten {@code (300, 300)} dann in der Mitte der Form.
* *
@@ -747,11 +1303,20 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
return ap; return ap;
} }
/**
* Speichert die aktuellen Einstellungen für Farben, Konturlinie und
* Schriftarten ab, sodass sie zu einem späteren Zeitpunkt mit
* {@link #popStyle()} wiederhergestellt werden können.
*/
public void pushStyle() { public void pushStyle() {
styleStack.push(shapeDelegate); styleStack.push(shapeDelegate);
shapeDelegate = new Text(shapeDelegate); shapeDelegate = new Text(shapeDelegate);
} }
/**
* Stellt die zuletzt mit {@link #pushStyle()} gespeicherten Einstellungen
* für Farben, Konturlinien und Texte wieder her.
*/
public void popStyle() { public void popStyle() {
if( styleStack.isEmpty() ) { if( styleStack.isEmpty() ) {
resetStyle(); resetStyle();
@@ -760,6 +1325,10 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
} }
} }
/**
* Setzt alle Einstellungen für Farben, Konturlinien und Texte auf die
* Standardwerte.
*/
public void resetStyle() { public void resetStyle() {
Text newDelegate = new Text(0, 0, ""); Text newDelegate = new Text(0, 0, "");
newDelegate.setFillColor(DEFAULT_FILLCOLOR); newDelegate.setFillColor(DEFAULT_FILLCOLOR);
@@ -770,34 +1339,92 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
shapeDelegate = newDelegate; shapeDelegate = newDelegate;
} }
/**
* Verschiebt den Ursprung der Zeichenebene um die angegebenen Werte entlang
* der x- und y-Achse.
*
* @param dx Verschiebung entlang der x-Achse.
* @param dy Verschiebung entlang der y-Achse.
*/
public void translate( double dx, double dy ) { public void translate( double dx, double dy ) {
drawing.translate(dx, dy); drawing.translate(dx, dy);
} }
/**
* Skaliert die Zeichenebene um den angegebenen Faktor.
* <p>
* Ein Abstand von zehn Einheiten wird bei einem Faktor von 2.0 zu einem
* Abstand von 20 Einheiten. Bei einem Faktor von 0.5 zu fünf Einheiten.
* <p>
* Der Skalierungsfaktor wird entlang der x- und y-Achse gleich angewandt.
*
* @param factor Der Skalierungsfaktor.
*/
public void scale( double factor ) { public void scale( double factor ) {
drawing.scale(factor, factor); drawing.scale(factor, factor);
} }
/**
* Rotiert die Zeichenebene um den angegebenen Winkel um den Ursprung.
*
* @param pAngle Rotationswinkel in Grad.
*/
public void rotate( double pAngle ) { public void rotate( double pAngle ) {
drawing.rotate(Math.toRadians(pAngle)); drawing.rotate(Math.toRadians(pAngle));
} }
/**
* Rotiert die Zeichenebene um den angegebenen Winkel um das angegebene
* Drehzentrum.
*
* @param pAngle Rotationswinkel in Grad.
* @param centerX x-Koordinate des Rotationszentrums.
* @param centerY y-Koordinate des Rotationszentrums.
*/
public void rotate( double pAngle, double centerX, double centerY ) { public void rotate( double pAngle, double centerX, double centerY ) {
drawing.rotate(Math.toRadians(pAngle), centerX, centerY); drawing.rotate(Math.toRadians(pAngle), centerX, centerY);
} }
/**
* Wendet eine Scherung auf die Zeichenebene an.
* <p>
* Bei einer Scherung werden die x-Koordinaten parallel zur x-Achse abhängig
* von ihrer y-Koordinate verschoben. Ebenso die y-Koordinaten parallel zur
* y-Achse abhängig von ihrer x-Koordinate.
*
* @param dx Scherungsfaktor entlang der x-Achse.
* @param dy Scherungsfaktor entlang der y-Achse.
*/
public void shear( double dx, double dy ) { public void shear( double dx, double dy ) {
drawing.shear(dx, dy); drawing.shear(dx, dy);
} }
/**
* @return Die aktuelle <a
* href="https://de.wikibrief.org/wiki/Transformation_matrix">Transformationsmatrix</a>
* der Zeichenebene.
*/
public AffineTransform getMatrix() { public AffineTransform getMatrix() {
return drawing.getTransform(); return drawing.getTransform();
} }
/**
* Speichert eine Kopie der aktuellen Transformationen der Zeichenebene ab.
* <p>
* Die zuletzt gespeicherten Transformationen können mit
* {@link #popMatrix()} wiederhergestellt werden.
*/
public void pushMatrix() { public void pushMatrix() {
transformStack.push(drawing.getTransform()); transformStack.push(drawing.getTransform());
} }
/**
* Ersetzt die aktuellen Transformationen der Zeichenebene durch die zuletzt
* mit {@link #pushMatrix()} gespeicherten.
* <p>
* Wurden keine Transformationen gespeichert, werden alle wird der
* Ursprungszustand wiederhergestellt.
*/
public void popMatrix() { public void popMatrix() {
if( transformStack.isEmpty() ) { if( transformStack.isEmpty() ) {
resetMatrix(); resetMatrix();
@@ -806,6 +1433,9 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
} }
} }
/**
* Setzt alle Transformationen der Zeichenebene zurück.
*/
public void resetMatrix() { public void resetMatrix() {
drawing.setTransform(new AffineTransform()); drawing.setTransform(new AffineTransform());
} }

View File

@@ -6,53 +6,121 @@ import schule.ngb.zm.util.io.ImageLoader;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Image; 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" ) @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();

View File

@@ -11,7 +11,7 @@ import java.util.*;
import java.util.function.DoubleUnaryOperator; import java.util.function.DoubleUnaryOperator;
/** /**
* Ein Layer um {@link Shape} Objekte zu zeichnen. * Eine Ebene um {@link Shape} Objekte zu zeichnen.
* <p> * <p>
* Ein {@code ShapesLayer} ist eine der drei Standardebenen der * Ein {@code ShapesLayer} ist eine der drei Standardebenen der
* {@link schule.ngb.zm.Zeichenmaschine}. * {@link schule.ngb.zm.Zeichenmaschine}.
@@ -19,6 +19,9 @@ import java.util.function.DoubleUnaryOperator;
@SuppressWarnings( "unused" ) @SuppressWarnings( "unused" )
public class ShapesLayer extends Layer { public class ShapesLayer extends Layer {
/**
*
*/
protected boolean clearBeforeDraw = true; protected boolean clearBeforeDraw = true;
private final List<Shape> shapes; private final List<Shape> shapes;

View File

@@ -12,6 +12,12 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Stack; import java.util.Stack;
/**
* 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" ) @SuppressWarnings( "unused" )
public class TurtleLayer extends Layer implements Strokeable, Fillable { public class TurtleLayer extends Layer implements Strokeable, Fillable {

View File

@@ -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 );
} }

View File

@@ -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 );
} }

View File

@@ -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;
}
}
} }

View File

@@ -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);
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;
} }

View File

@@ -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);
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);
} }

View File

@@ -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++ ) {

View File

@@ -1,6 +1,9 @@
package schule.ngb.zm.shapes; package schule.ngb.zm.shapes;
import schule.ngb.zm.*; import schule.ngb.zm.BasicDrawable;
import schule.ngb.zm.Fillable;
import schule.ngb.zm.Options;
import schule.ngb.zm.Strokeable;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
@@ -142,9 +145,6 @@ public abstract class Shape extends BasicDrawable {
*/ */
public abstract double getHeight(); public abstract double getHeight();
/**
* {@inheritDoc}
*/
@Override @Override
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to, Options.Direction dir ) { public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to, Options.Direction dir ) {
Point2D apDir = getAbsAnchorPoint(dir); Point2D apDir = getAbsAnchorPoint(dir);
@@ -152,9 +152,6 @@ public abstract class Shape extends BasicDrawable {
setGradient(apInv.getX(), apInv.getY(), from, apDir.getX(), apDir.getY(), to); setGradient(apInv.getX(), apInv.getY(), from, apDir.getX(), apDir.getY(), to);
} }
/**
* {@inheritDoc}
*/
@Override @Override
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to ) { public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to ) {
Point2D ap = getAbsAnchorPoint(CENTER); Point2D ap = getAbsAnchorPoint(CENTER);

View File

@@ -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));
} }

View File

@@ -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;

View File

@@ -5,7 +5,7 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
/** /**
* Eine Hilfsklasse um Dinge zu zählen. * Eine Helferklasse, um Dinge zu zählen.
* <p> * <p>
* Im einfachsten Fall kann der Zähler als geteilte Zählvariable genutzt werden, * Im einfachsten Fall kann der Zähler als geteilte Zählvariable genutzt werden,
* die mit {@link #inc()} und {@link #dec()} aus verschiedenen Objekten oder * die mit {@link #inc()} und {@link #dec()} aus verschiedenen Objekten oder
@@ -13,11 +13,11 @@ import java.util.concurrent.atomic.AtomicInteger;
* <p> * <p>
* Der Zähler kann aber auch Objekte zählen, indem die Instanzen an * Der Zähler kann aber auch Objekte zählen, indem die Instanzen an
* {@link #count(Object)} übergeben werden. Am Ende kann mit {@link #getCount()} * {@link #count(Object)} übergeben werden. Am Ende kann mit {@link #getCount()}
* die Anzahl der Objkete abgerufen werden. * die Anzahl der Objekte abgerufen werden.
* <p> * <p>
* Handelt es sich bei den Objekten um Zahlen, dann merkt sich ein Zähler auch * Handelt es sich bei den Objekten um Zahlen, dann merkt sich ein
* das Maximum, das Minimum, die Summe und den Durchschnitt der gezählten * {@code Counter} auch das Maximum, das Minimum, die Summe und den Durchschnitt
* Werte. * der gezählten Werte.
* <p> * <p>
* Ein Zähler kann auch komplette Arrays oder Listen von Zahlen zählen und die * Ein Zähler kann auch komplette Arrays oder Listen von Zahlen zählen und die
* obigen Statistiken auswerten. * obigen Statistiken auswerten.
@@ -96,31 +96,42 @@ public final class Counter {
} }
/** /**
* Gibt die aktuelle Anzahl zurück.
* @return Die aktuelle Anzahl. * @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;
@@ -129,13 +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" ) @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);
@@ -147,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();
@@ -173,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 ) {
@@ -188,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 ) {
@@ -196,6 +266,13 @@ 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" ) @SuppressWarnings( "UnusedReturnValue" )
public synchronized Counter countAll( int[] values ) { public synchronized Counter countAll( int[] values ) {
for( double value : values ) { for( double value : values ) {
@@ -204,6 +281,13 @@ public final class Counter {
return this; 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 ) {
@@ -212,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 ) {

View File

@@ -2,8 +2,6 @@ package schule.ngb.zm.util;
import schule.ngb.zm.Color; import schule.ngb.zm.Color;
import schule.ngb.zm.Constants; import schule.ngb.zm.Constants;
import schule.ngb.zm.Zeichenmaschine;
import schule.ngb.zm.util.io.FileLoader;
import schule.ngb.zm.util.io.ImageLoader; import schule.ngb.zm.util.io.ImageLoader;
import java.awt.Font; import java.awt.Font;
@@ -11,46 +9,69 @@ import java.awt.FontMetrics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.font.LineMetrics; import java.awt.font.LineMetrics;
import java.awt.image.BufferedImage; 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.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;
/** /**
* Hilfsklasse, um zufällige Beispieldaten zu erzeugen. * Hilfsklasse, um zufällige Beispieldaten zu erzeugen.
* <p> * <p>
* Die Klasse kann verschiedene Arten realistischer Beispieldaten erzeugen, * Die Klasse kann verschiedene Arten realistischer Beispieldaten erzeugen,
* unter anderem Namen, E-Mail-Adressen, Passwörter oder Platzhalterbilder. * unter anderem Namen, E-Mail-Adressen, Passwörter oder Platzhalter-Bilder.
*/ */
@SuppressWarnings( "unused" )
public final class Faker { public final class Faker {
public static final String FAKE_IMG_URL = "https://loremflickr.com/%d/%d"; public static final String FAKE_IMG_URL = "https://loremflickr.com/%d/%d";
public static void main( String[] args ) {
String text = Faker.fakeText(2000, 8);
System.out.println(text);
}
/** /**
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Nutzernamen. * 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 n Anzahl der Beispieldaten. * @param count Anzahl der Beispieldaten.
* @return Ein Array mit den Beispieldaten. * @return Ein Array mit den Beispieldaten.
*/ */
public static String[][] fakeUsers( int n ) { public static String[][] fakeUsers( int count ) {
String[][] data = loadMockfile("users"); return randomSample("users", count, ( line ) -> line.split(","), String[].class);
String[][] result = new String[n][];
for( int i = 0; i < n; i++ ) {
result[i] = Constants.choice(data);
}
return result;
} }
/** /**
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Vornamen. * Erzeugt ein Array mit den angegebenen Anzahl zufälliger Vornamen.
* *
* @param n Anzahl der Beispieldaten. * @param count Anzahl der Beispieldaten.
* @return Ein Array mit den Beispieldaten. * @return Ein Array mit den Beispieldaten.
*/ */
public static String[] fakeNames( int n ) { public static String[] fakeNames( int count ) {
String[][] data = loadMockfile("users"); return randomSample("users", count, ( line ) -> line.split(",")[0], String.class);
String[] result = new String[n];
for( int i = 0; i < n; i++ ) {
String[] row = Constants.choice(data);
result[i] = row[0];
}
return result;
} }
@@ -58,65 +79,187 @@ public final class Faker {
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Namen (Vor- und * Erzeugt ein Array mit den angegebenen Anzahl zufälliger Namen (Vor- und
* Nachname). * Nachname).
* *
* @param n Anzahl der Beispieldaten. * @param count Anzahl der Beispieldaten.
* @return Ein Array mit den Beispieldaten. * @return Ein Array mit den Beispieldaten.
*/ */
public static String[] fakeFullnames( int n ) { public static String[] fakeFullnames( int count ) {
String[][] data = loadMockfile("users"); return randomSample("users", count, ( line ) -> {
String[] result = new String[n]; String[] parts = line.split(",");
for( int i = 0; i < n; i++ ) { return parts[0] + " " + parts[1];
String[] row = Constants.choice(data); }, String.class);
result[i] = row[0] + " " + row[1];
}
return result;
} }
/** /**
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Nutzernamen. * Erzeugt ein Array mit den angegebenen Anzahl zufälliger Nutzernamen.
* *
* @param n Anzahl der Beispieldaten. * @param count Anzahl der Beispieldaten.
* @return Ein Array mit den Beispieldaten. * @return Ein Array mit den Beispieldaten.
*/ */
public static String[] fakeUsernames( int n ) { public static String[] fakeUsernames( int count ) {
String[][] data = loadMockfile("users"); return randomSample("users", count, ( line ) -> line.split(",")[3], String.class);
String[] result = new String[n];
for( int i = 0; i < n; i++ ) {
String[] row = Constants.choice(data);
result[i] = row[3];
}
return result;
} }
/** /**
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Passwörter. * Erzeugt ein Array mit den angegebenen Anzahl zufälliger Passwörter.
* *
* @param n Anzahl der Beispieldaten. * @param count Anzahl der Beispieldaten.
* @return Ein Array mit den Beispieldaten. * @return Ein Array mit den Beispieldaten.
*/ */
public static String[] fakePasswords( int n ) { public static String[] fakePasswords( int count ) {
String[][] data = loadMockfile("users"); return randomSample("users", count, ( line ) -> line.split(",")[4], String.class);
String[] result = new String[n];
for( int i = 0; i < n; i++ ) {
String[] row = Constants.choice(data);
result[i] = row[4];
}
return result;
} }
/** /**
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger E-Mail-Adressen. * Erzeugt ein Array mit den angegebenen Anzahl zufälliger E-Mail-Adressen.
* *
* @param n Anzahl der Beispieldaten. * @param count Anzahl der Beispieldaten.
* @return Ein Array mit den Beispieldaten. * @return Ein Array mit den Beispieldaten.
*/ */
public static String[] fakeEmails( int n ) { public static String[] fakeEmails( int count ) {
String[][] data = loadMockfile("users"); return randomSample("users", count, ( line ) -> line.split(",")[5], String.class);
String[] result = new String[n];
for( int i = 0; i < n; i++ ) {
String[] row = Constants.choice(data);
result[i] = row[5];
} }
/**
* 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 Date}-Objekte.
*
* @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;
}
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;
}
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;
}
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;
}
public static List<Integer> fakeIntegerList( int count, int min, int max, List<Integer> list ) {
if( list == null ) {
list = new ArrayList<>(count);
}
for( int i = 0; i < count; i++ ) {
list.add(random(min, max));
}
return list;
}
@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; return result;
} }
@@ -165,7 +308,7 @@ public final class Faker {
FontMetrics fontMerics = graphics.getFontMetrics(); FontMetrics fontMerics = graphics.getFontMetrics();
LineMetrics lineMetrics = fontMerics.getLineMetrics(text, graphics); LineMetrics lineMetrics = fontMerics.getLineMetrics(text, graphics);
graphics.drawString(text, graphics.drawString(text,
(int) ((width - fontMerics.stringWidth(text)) / 2), (width - fontMerics.stringWidth(text)) / 2,
(int) (height / 2 - lineMetrics.getDescent() + lineMetrics.getAscent() / 2) (int) (height / 2 - lineMetrics.getDescent() + lineMetrics.getAscent() / 2)
); );
graphics.dispose(); graphics.dispose();
@@ -176,15 +319,9 @@ public final class Faker {
} }
} }
public static String fakeText( int words, int paragraphs ) {
return "";
}
private static String[][] loadMockfile( String name ) {
return FileLoader.loadCsv("schule/ngb/zm/util/mock-" + name + ".csv", true);
}
private Faker() { private Faker() {
} }
private static final Log LOG = Log.getLogger(Faker.class);
} }

View File

@@ -3,7 +3,7 @@ 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
@@ -18,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;

View File

@@ -3,13 +3,13 @@ package schule.ngb.zm.util;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* Eine Hilfsklasse zur Zeitmessung im Nanosekundenbereich. * Helferklasse zur Zeitmessung im Nanosekundenbereich.
* <p> * <p>
* Die Klasse kann zum Beispiel genutzt werden, um die Laufzeit eines * Mit einem {@code Timer} kann zum Beispiel die Laufzeit eines Algorithmus
* Quelltextes zu messen. Wie eine echte Stoppuhr läuft der {@code Timer} * gemessen werden. Wie eine echte Stoppuhr läuft der {@code Timer} weiter, wenn
* weiter, wenn nach einem {@link #stop()} wieder {@link #start()} aufgerufen * nach einem {@link #stop()} wieder {@link #start()} aufgerufen wird. Soll die
* wird. Soll die Zeitmessung wieder bei null beginnen, muss vorher * Zeitmessung wieder bei null beginnen, muss vorher {@link #reset()} genutzt
* {@link #reset()} genutzt werden. * werden.
* <p> * <p>
* Die gemessene Zeit kann in {@link #getMillis() Millisekunden} oder * Die gemessene Zeit kann in {@link #getMillis() Millisekunden} oder
* {@link #getSeconds() Sekunden} abgerufen werden. Wird eine noch größere * {@link #getSeconds() Sekunden} abgerufen werden. Wird eine noch größere
@@ -100,7 +100,7 @@ public final class Timer {
} }
/** /**
* Stoppt den {@code Timer}, wenn er derzeit läuft. Die gemessene Zeit wird * Stoppt den {@code Timer}, wenn er derzeit läuft. Die gemessene Dauer wird
* zur Summe aller gemessenen Zeiten hinzuaddiert. * zur Summe aller gemessenen Zeiten hinzuaddiert.
* *
* @return Dieser {@code Timer} selbst (method chaining). * @return Dieser {@code Timer} selbst (method chaining).
@@ -116,8 +116,8 @@ public final class Timer {
/** /**
* Setzt den {@code Timer} auf den Startzustand und löscht alle bisher * Setzt den {@code Timer} auf den Startzustand und löscht alle bisher
* gemessenen Zeiten. Falls die Zeitmessung gerade läuft, wird sie nicht * gemessenen Zeiten. Falls die Zeitmessung gerade läuft, stoppt sie nicht,
* gestoppt, sondern läuft vom Zeitpunkt des Aufrufs weiter. * sondern startet vom Zeitpunkt des Aufrufs neu.
* *
* @return Dieser {@code Timer} selbst (method chaining). * @return Dieser {@code Timer} selbst (method chaining).
*/ */

View File

@@ -3,6 +3,9 @@ package schule.ngb.zm.util;
import java.util.Objects; import java.util.Objects;
import java.util.function.Supplier; import java.util.function.Supplier;
/**
* Statische Methoden, um Methodenparameter auf Gültigkeit zu prüfen.
*/
@SuppressWarnings( "UnusedReturnValue" ) @SuppressWarnings( "UnusedReturnValue" )
public class Validator { public class Validator {

View File

@@ -7,6 +7,60 @@ 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&lt;MyEvent, MyEventListener&gt; dispatcher = new EventDispatcher&lt;&gt;();
* 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>
*
* @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 final CopyOnWriteArraySet<L> listeners; private final CopyOnWriteArraySet<L> listeners;
@@ -35,6 +89,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();
} }

View File

@@ -1,5 +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> {
} }

View File

@@ -4,37 +4,63 @@ import schule.ngb.zm.util.Log;
import java.io.BufferedReader; 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.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
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.stream.Stream; 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 Datei Zeilenweise in ein Array. * 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 * @param source Die Quelle der Textdatei.
* @param charset * @param charset Das zu verwendene {@code Charset}.
* @return * @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( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) { try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) {
@@ -52,10 +78,28 @@ public final class FileLoader {
} }
} }
/**
* 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( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) { try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) {
StringBuilder result = new StringBuilder(); StringBuilder result = new StringBuilder();
@@ -73,15 +117,16 @@ public final class FileLoader {
} }
/** /**
* Lädt die Daten aus einer CSV Datei in ein zweidimensionales * Lädt die Daten aus einer CSV-Datei in ein zweidimensionales
* String-Array. * String-Array.
* <p> * <p>
* Der Aufruf entspricht dem Aufruf von * Der Aufruf entspricht
* <pre><code> * <pre><code>
* FileLoader.loadCsv(source, ',', skipFirst, UTF8); * FileLoader.loadCsv(source, ',', skipFirst, UTF8);
* </code></pre> * </code></pre>
* *
* @param source Die Quelle der CSV-Daten. * @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. * @param skipFirst Ob die erste Zeile übersprungen werden soll.
* @return Ein Array mit den Daten als {@code String}s. * @return Ein Array mit den Daten als {@code String}s.
* @see #loadCsv(String, char, boolean, Charset) * @see #loadCsv(String, char, boolean, Charset)
@@ -91,12 +136,12 @@ public final class FileLoader {
} }
/** /**
* Lädt die Daten aus einer CSV Datei in ein zweidimensionales * Lädt die Daten aus einer CSV-Datei in ein zweidimensionales
* String-Array. * String-Array.
* <p> * <p>
* Die Methode ist nicht in der Lage, komplexe CSV-Dateien zu verarbeiten. * Die Methode ist nicht in der Lage, komplexe CSV-Dateien zu verarbeiten.
* Insbesondere können Inhalte, die das Trennzeichen {@code separator} * Insbesondere werden Inhalte, die das Trennzeichen {@code separator}
* enthalten, nicht korrekt erkannt werden. Das Trennzeichen wird unabhängig * enthalten, nicht korrekt erkannt. Das Trennzeichen wird unabhängig
* vom Kontext immer als Zelltrenner erkannt. (Im Normalfall kann das * vom Kontext immer als Zelltrenner erkannt. (Im Normalfall kann das
* Trennzeichen durch die Verwendung doppelter Anführungszeichen in der Art * Trennzeichen durch die Verwendung doppelter Anführungszeichen in der Art
* {@code Inhalt,"Inhalt, der Komma enthält",Inhalt} maskiert werden.) * {@code Inhalt,"Inhalt, der Komma enthält",Inhalt} maskiert werden.)
@@ -161,7 +206,7 @@ public final class FileLoader {
).toArray(double[][]::new); ).toArray(double[][]::new);
} }
public FileLoader() { private FileLoader() {
} }
private static final Log LOG = Log.getLogger(FileLoader.class); private static final Log LOG = Log.getLogger(FileLoader.class);

View File

@@ -1,12 +1,14 @@
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.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -18,8 +20,13 @@ 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 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> * <p>
* Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen * 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
@@ -27,7 +34,8 @@ public class FontLoader {
* 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;
@@ -45,10 +53,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);
@@ -85,6 +91,38 @@ 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() {
} }

View File

@@ -13,39 +13,38 @@ import java.nio.file.Files;
import java.nio.file.Path; 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 {
/** /**
* Ermittelt zur angegebenen Quelle einen passenden {@link URL} (<em>Unified * Ermittelt zur angegebenen Quelle einen passenden {@link URL} (<em>Unified
* Resource Locator</em>). Eine passende Datei-Resource wird wie folgt * Resource Locator</em>). Eine passende Datei-Ressource wird wie folgt
* ermittelt: * ermittelt:
* <ol> * <ol>
* <li>Ist {@code source} eine existierende Datei * <li>Ist {@code source} eine existierende Datei
* ({@code new File(source}.isFile() == true})?</li> * ({@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} ein relativer Pfad im Projekt ({@code getResource(source) != null})?</li>
* <li>Ist {@code source} im Classpath enthalten ({@code getClassLoader().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> * <li>Ansonsten erstelle ein {@link URL}-Objekt.</li>
* </ol> * </ol>
* <p> * <p>
* Ein {@code URL} für die erste gefundene Resource wird zurückgegeben. * Ein {@code URL} für die erste gefundene Resource wird zurückgegeben.
* Auftretende Exceptions
* werden als {@link IOException} geworfen.
* <p> * <p>
* Bei einer Exception werden die folgenden Quellen nicht mehr abgefragt. * 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} * Eine {@link java.net.MalformedURLException} beim Konstruieren des {@code URL}
* zu einer Datei verhindert daher, dass noch im Classpath gesucht wird. * 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 oder Netzwerkressource)
* @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 Erzeugen einer URL zu * @throws IOException Geworfen beim Erzeugen einer URL zu
* einer bestehenden Resource. * einer bestehenden Ressource.
*/ */
public static URL getResourceURL( String source ) throws NullPointerException, IllegalArgumentException, IOException { public static URL getResourceURL( String source ) throws NullPointerException, IllegalArgumentException, IOException {
Validator.requireNotNull(source, "Resource source may not be null"); Validator.requireNotNull(source, "Resource source may not be null");
@@ -66,12 +65,12 @@ public class ResourceStreamProvider {
} }
/** /**
* Sucht eine zur angegebenen Quelle passende Resource und öffnet einen * Sucht eine zur angegebenen Quelle passende Ressource und öffnet einen
* passenden {@link InputStream}. Die konkrete Art des Streams hängt davon * passenden {@link InputStream}. Die konkrete Art des Streams hängt davon
* ab, welche Art an Resource gefunden wird: * ab, welche Art an Ressource gefunden wird:
* <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}, 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>
@@ -81,35 +80,44 @@ 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 {
return getResourceURL(source).openStream(); return getResourceURL(source).openStream();
} }
/** /**
* @param source * Sucht eine zur angegebenen Quelle passende Ressource und öffnet einen
* @return * passenden {@link OutputStream}.
* @throws IOException * <p>
* Es wird davon ausgegangen, dass die Quelle auf eine Datei verweist, auf
* die Schreibrechte bestehen. Ist dies nicht der Fall, wird eine
* {@link IOException} geworfen.
*
* @param source Eine Quelle für die Ressource (Absoluter Dateipfad,
* Dateipfad im Classpath).
* @return {@code OutputStream} für die Ressource.
* @throws IOException Falls die Quelle kein gültiges Ziel beschreibt.
*/ */
public static OutputStream getOutputStream( String source ) throws IOException { public static OutputStream getOutputStream( String source ) throws IOException {
try { try {
@@ -119,22 +127,83 @@ public class ResourceStreamProvider {
} }
} }
/**
* Sucht eine zur angegebenen Quelle passende Ressource und öffnet einen
* {@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 { public static BufferedReader getReader( String source ) throws IOException {
return getReader(source, StandardCharsets.UTF_8); return getReader(source, StandardCharsets.UTF_8);
} }
/**
* 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 { public static BufferedReader getReader( String source, Charset charset ) throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream(source), charset.newDecoder())); return new BufferedReader(new InputStreamReader(getInputStream(source), charset.newDecoder()));
} }
/**
* 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 { public static BufferedWriter getWriter( String source ) throws IOException {
return getWriter(source, StandardCharsets.UTF_8); 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 { public static BufferedWriter getWriter( String source, Charset charset ) throws IOException {
return new BufferedWriter(new OutputStreamWriter(getOutputStream(source), charset.newEncoder())); return new BufferedWriter(new OutputStreamWriter(getOutputStream(source), charset.newEncoder()));
} }
// Privater C'tor
private ResourceStreamProvider() { private ResourceStreamProvider() {
} }

View 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
1 algae green #64e986
2 aliceblue #f0f8ff
3 alien green #6cc417
4 antiquewhite #faebd7
5 aquamarine #7fffd4
6 army brown #827b60
7 ash gray #666362
8 avocado green #b2c248
9 aztech purple #893bff
10 azure #f0ffff
11 baby blue #95b9c7
12 bashful pink #c25283
13 basket ball orange #f88158
14 battleship gray #848482
15 bean red #f75d59
16 bee yellow #e9ab17
17 beer #fbb117
18 beetle green #4c787e
19 beige #f5f5dc
20 black cat #413839
21 black cow #4c4646
22 black eel #463e3f
23 black #000000
24 blanchedalmond #ffebcd
25 blonde #fbf6d9
26 blood red #7e3517
27 blossom pink #f9b7ff
28 blue angel #b7ceec
29 blue diamond #4ee2ec
30 blue dress #157dec
31 blue eyes #1569c7
32 blue gray #98afc7
33 blue green #7bccb5
34 blue hosta #77bfc7
35 blue ivy #3090c7
36 blue jay #2b547e
37 blue koi #659ec7
38 blue lagoon #8eebec
39 blue lotus #6960ec
40 blue orchid #1f45fc
41 blue ribbon #306eff
42 blue whale #342d7e
43 blue zircon #57feff
44 blueberry blue #0041c2
45 blush pink #e6a9ec
46 blush red #e56e94
47 brass #b5a642
48 bright gold #fdd017
49 bright neon pink #f433ff
50 bronze #cd7f32
51 brown bear #835c3b
52 brown sugar #e2a76f
53 bullet shell #af9b60
54 burgundy #8c001a
55 burlywood #deb887
56 burnt pink #c12267
57 butterfly blue #38acec
58 cadillac pink #e38aae
59 camel brown #c19a6b
60 camouflage green #78866b
61 cantaloupe #ffa62f
62 caramel #c68e17
63 carbon gray #625d5d
64 carnation pink #f778a1
65 celeste #50ebec
66 champagne #f7e7ce
67 charcoal #34282c
68 chartreuse #8afb17
69 cherry red #c24641
70 chestnut red #c34a2c
71 chestnut #954535
72 chilli pepper #c11b17
73 chocolate #c85a17
74 cinnamon #c58917
75 cloudy gray #6d6968
76 clover green #3ea055
77 cobalt blue #0020c2
78 coffee #6f4e37
79 columbia blue #87afc7
80 construction cone orange #f87431
81 cookie brown #c7a317
82 copper #b87333
83 coral blue #afdcec
84 coral #ff7f50
85 corn yellow #fff380
86 cornflower blue #6495ed
87 cornsilk #fff8dc
88 cotton candy #fcdfff
89 cranberry #9f000f
90 cream #ffffcc
91 crimson #e238ec
92 crocus purple #9172ec
93 crystal blue #5cb3ff
94 cyan opaque #92c7c7
95 cyan or aqua #00ffff
96 dark carnation pink #c12283
97 dark forest green #254117
98 dark goldenrod #af7817
99 dark orange #f88017
100 dark orchid #7d1b7e
101 dark salmon #e18b6b
102 dark sea green #8bb381
103 dark slate blue #2b3856
104 dark slate grey #25383c
105 dark turquoise #3b9c9c
106 dark violet #842dce
107 day sky blue #82caff
108 deep peach #ffcba4
109 deep pink #f52887
110 deep sky blue #3bb9ff
111 denim blue #79baec
112 denim dark blue #151b8d
113 desert sand #edc9af
114 dimorphotheca magenta #e3319d
115 dodger blue #1589ff
116 dollar bill green #85bb65
117 dragon green #6afb92
118 dull purple #7f525d
119 earth blue #0000a0
120 eggplant #614051
121 electric blue #9afeff
122 emerald green #5ffb17
123 fall leaf brown #c8b560
124 fern green #667c26
125 ferrari red #f70d1a
126 fire engine red #f62817
127 firebrick #800517
128 flamingo pink #f9a7b0
129 forest green #4e9258
130 frog green #99c68e
131 ginger brown #c9be62
132 glacial blue ice #368bc1
133 golden brown #eac117
134 goldenrod #edda74
135 granite #837e7c
136 grape #5e5a80
137 grapefruit #dc381f
138 gray cloud #b6b6b4
139 gray dolphin #5c5858
140 gray goose #d1d0ce
141 gray wolf #504a4b
142 gray #736f6e
143 grayish turquoise #5e7d7e
144 green apple #4cc417
145 green onion #6aa121
146 green peas #89c35c
147 green snake #6cbb3c
148 green thumb #b5eaaa
149 green yellow #b1fb17
150 green #00ff00
151 greenish blue #307d7e
152 gunmetal #2c3539
153 halloween orange #e66c2c
154 harvest gold #ede275
155 hazel green #617c58
156 heliotrope purple #d462ff
157 hot pink #f660ab
158 hummingbird green #7fe817
159 iceberg #56a5ec
160 iguana green #9cb071
161 indigo #4b0082
162 iridium #3d3c3a
163 jade green #5efb6e
164 jasmine purple #a23bec
165 jeans blue #a0cfec
166 jellyfish #46c7c7
167 jet gray #616d7e
168 jungle green #347c2c
169 kelly green #4cc552
170 khaki rose #c5908e
171 khaki #ada96e
172 lapis blue #15317e
173 lava red #e42217
174 lavender blue #e3e4fa
175 lavender pinocchio #ebdde2
176 lawn green #87f717
177 lemon chiffon #fff8c6
178 light aquamarine #93ffe8
179 light blue #addfff
180 light coral #e77471
181 light cyan #e0ffff
182 light jade #c3fdb8
183 light pink #faafba
184 light salmon #f9966b
185 light sea green #3ea99f
186 light sky blue #82cafa
187 light slate blue #736aff
188 light slate gray #6d7b8d
189 light slate #ccffff
190 light steel blue #728fce
191 lilac #c8a2c8
192 lime green #41a317
193 lipstick pink #c48793
194 love red #e41b17
195 lovely purple #7f38ec
196 macaroni and cheese #f2bb66
197 macaw blue green #43bfc7
198 magenta #ff00ff
199 mahogany #c04000
200 mango orange #ff8040
201 marble blue #566d7e
202 maroon #810541
203 mauve #e0b0ff
204 medium aquamarine #348781
205 medium forest green #347235
206 medium orchid #b048b5
207 medium purple #8467d7
208 medium sea green #306754
209 medium spring green #348017
210 medium turquoise #48cccd
211 medium violet red #ca226b
212 metallic silver #bcc6cc
213 midnight blue #151b54
214 midnight #2b1b17
215 milk white #fefcff
216 mint green #98ff98
217 mist blue #646d7e
218 misty rose #fbbbb9
219 moccasin #827839
220 mocha #493d26
221 mustard #ffdb58
222 navy blue #000080
223 nebula green #59e817
224 neon pink #f535aa
225 night #0c090a
226 northern lights blue #78c7c7
227 oak brown #806517
228 ocean blue #2b65ec
229 oil #3b3131
230 orange gold #d4a017
231 orange salmon #c47451
232 pale blue lily #cfecec
233 pale violet red #d16587
234 papaya orange #e56717
235 parchment #ffffc2
236 pastel blue #b4cfec
237 peach #ffe5b4
238 pearl #fdeef4
239 periwinkle #e9cfec
240 pig pink #fdd7e4
241 pine green #387c44
242 pink bow #c48189
243 pink bubblegum #ffdfdd
244 pink cupcake #e45e9d
245 pink daisy #e799a3
246 pink lemonade #e4287c
247 pink rose #e7a1b0
248 pink #faafbe
249 pistachio green #9dc209
250 platinum #e5e4e2
251 plum pie #7d0541
252 plum purple #583759
253 plum velvet #7d0552
254 plum #b93b8f
255 powder blue #c6deff
256 puce #7f5a58
257 pumpkin orange #f87217
258 purple amethyst #6c2dc7
259 purple daffodil #b041ff
260 purple dragon #c38ec7
261 purple flower #a74ac7
262 purple haze #4e387e
263 purple iris #571b7e
264 purple jam #6a287e
265 purple mimosa #9e7bff
266 purple monster #461b7e
267 purple sage bush #7a5dc7
268 purple #8e35ef
269 red dirt #7f5217
270 red fox #c35817
271 red wine #990012
272 red #ff0000
273 robin egg blue #bdedff
274 rogue pink #c12869
275 rose gold #ecc5c0
276 rose #e8adaa
277 rosy brown #b38481
278 rosy finch #7f4e52
279 royal blue #2b60de
280 rubber ducky yellow #ffd801
281 ruby red #f62217
282 rust #c36241
283 saffron #fbb917
284 sage green #848b79
285 salad green #a1c935
286 sand #c2b280
287 sandstone #786d5f
288 sandy brown #ee9a4d
289 sangria #7e3817
290 sapphire blue #2554c7
291 scarlet #ff2400
292 school bus yellow #e8a317
293 sea blue #c2dfff
294 sea green #4e8975
295 sea turtle green #438d80
296 seashell #fff5ee
297 seaweed green #437c17
298 sedona #cc6600
299 sepia #7f462c
300 shamrock green #347c17
301 shocking orange #e55b3c
302 sienna #8a4117
303 silk blue #488ac7
304 sky blue #6698ff
305 slate blue #737ca1
306 slate gray #657383
307 slime green #bce954
308 smokey gray #726e6d
309 spring green #4aa02c
310 steel blue #4863a0
311 stoplight go green #57e964
312 sun yellow #ffe87c
313 sunrise orange #e67451
314 tan brown #ece5b6
315 tangerine #e78a61
316 taupe #483c32
317 tea green #ccfb5d
318 teal #008080
319 thistle #d2b9d3
320 tiffany blue #81d8d0
321 tiger orange #c88141
322 tron blue #7dfdfe
323 tulip pink #c25a7c
324 turquoise #43c6db
325 tyrian purple #c45aec
326 valentine red #e55451
327 vampire gray #565051
328 vanilla #f3e5ab
329 velvet maroon #7e354d
330 venom green #728c00
331 viola purple #7e587e
332 violet red #f6358a
333 violet #8d38c9
334 water #ebf4fa
335 watermelon pink #fc6c85
336 white #ffffff
337 windows blue #357ec7
338 wisteria purple #c6aec7
339 wood #966f33
340 yellow green #52d017
341 yellow #ffff00
342 zombie green #54c571

View File

@@ -0,0 +1 @@
Weit hinten, hinter den Wortbergen, fern der Länder Vokalien und Konsonantien leben die Blindtexte. Abgeschieden wohnen Sie in Buchstabhausen an der Küste des Semantik, eines großen Sprachozeans. Ein kleines Bächlein namens Duden fließt durch ihren Ort und versorgt sie mit den nötigen Regelialien. Es ist ein paradiesmatisches Land, in dem einem gebratene Satzteile in den Mund fliegen. Nicht einmal von der allmächtigen Interpunktion werden die Blindtexte beherrscht - ein geradezu unorthographisches Leben. Eines Tages aber beschloß eine kleine Zeile Blindtext, ihr Name war Lorem Ipsum, hinaus zu gehen in die weite Grammatik. Der große Oxmox riet ihr davon ab, da es dort wimmele von bösen Kommata, wilden Fragezeichen und hinterhältigen Semikoli, doch das Blindtextchen ließ sich nicht beirren. Es packte seine sieben Versalien, schob sich sein Initial in den Gürtel und machte sich auf den Weg. Als es die ersten Hügel des Kursivgebirges erklommen hatte, warf es einen letzten Blick zurück auf die Skyline seiner Heimatstadt Buchstabhausen, die Headline von Alphabetdorf und die Subline seiner eigenen Straße, der Zeilengasse. Wehmütig lief ihm eine rethorische Frage über die Wange, dann setzte es seinen Weg fort. Unterwegs traf es eine Copy. Die Copy warnte das Blindtextchen, da, wo sie herkäme wäre sie zigmal umgeschrieben worden und alles, was von ihrem Ursprung noch übrig wäre, sei das Wort "und" und das Blindtextchen solle umkehren und wieder in sein eigenes, sicheres Land zurückkehren. Doch alles Gutzureden konnte es nicht überzeugen und so dauerte es nicht lange, bis ihm ein paar heimtückische Werbetexter auflauerten, es mit Longe und Parole betrunken machten und es dann in ihre Agentur schleppten, wo sie es für ihre Projekte wieder und wieder mißbrauchten. Und wenn es nicht umgeschrieben wurde, dann benutzen Sie es immernoch.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,949 @@
Abbau
Abend
Abzug
Adler
Adolf
Agent
Akten
Aktie
Alarm
Album
Allee
Alpen
Ampel
Amtes
Anbau
Angst
Anruf
Anton
Anzug
Apple
April
Areal
Arena
Armee
Armut
Arten
Asien
Athen
Augen
Autor
Autos
Baden
Bande
Bands
Basel
Basis
Bauch
Bauer
Bayer
Beate
Beide
Beine
Beleg
Bernd
Beruf
Beste
Beton
Beute
Bevor
Bezug
Bibel
Bitte
Blatt
Blick
Block
Blues
Bogen
Bombe
Boris
Brand
Braun
Brief
Bruch
Bruno
Brust
Buben
Buche
Busse
Bäume
Böden
Börse
Bühne
Büros
Chaos
Chefs
Chile
China
Clubs
Coach
Daher
Damen
Daran
Darum
Datei
Daten
Datum
Dauer
David
Davon
Decke
Deren
Diana
Dinge
Donau
Doris
Drama
Dreck
Druck
Duell
Durch
Ecken
Edith
Eiern
Eisen
Elite
Engel
Enkel
Erbin
Erich
Erika
Erlös
Ernst
Erste
Erwin
Essen
Ethik
Fahne
Fahrt
Falls
Farbe
Faust
Fazit
Feier
Feind
Felix
Ferne
Feuer
Figur
Filme
Films
Firma
Fisch
Flüge
Focus
Folge
Fonds
Forum
Fotos
Foyer
Franc
Frank
Franz
Frist
Fritz
Front
Frust
Fuchs
Fulda
Fälle
Fülle
Füßen
Geben
Gebot
Gehen
Gehör
Geist
Georg
Gerda
Gerät
Geste
Glanz
Glück
Gnade
Gramm
Griff
Group
Grund
Grüne
Gunst
Gutes
Gäste
Güter
Haare
Haars
Hafen
Halle
Hanau
Handy
Harry
Hauch
Hause
Hebel
Heike
Heims
Heinz
Helga
Henry
Herrn
Hilfe
Hitze
Horst
Hosen
Hotel
Huber
Humor
Hunde
Hände
Härte
Hätte
Höhen
Hölle
Hörer
Hügel
Hürde
Hütte
Ideen
Image
Index
Indiz
Infos
Insel
Intel
Irene
Islam
Jahre
Jakob
James
Japan
Jeans
Jesus
Josef
Jubel
Juden
Julia
Jungs
Jutta
Jäger
Kabel
Kabul
Kader
Kairo
Kampf
Kanal
Karin
Karte
Kasse
Kenia
Kette
Kinos
Kirch
Klage
Klaus
Klima
Klubs
Kohle
Kohls
Konto
Kopie
Korea
Kraft
Krebs
Kreis
Kreml
Kreuz
Krieg
Kripo
Krise
Krone
Krupp
Kugel
Kunde
Kunst
Kurse
Kurve
König
Köpfe
Küche
Kürze
Küste
Labor
Lager
Laien
Lande
Lange
Laufe
Laune
Leder
Legen
Lehre
Leser
Leute
Licht
Linie
Linke
Liste
Liter
Logik
Lokal
Luxus
Lyrik
Löhne
Löwen
Lücke
Macht
Mafia
Magen
Mainz
Major
Maler
Maria
Marie
Mario
Marke
Markt
Maske
Masse
Match
Mauer
Media
Meist
Menge
Meter
Metro
Meyer
Miami
Milch
Minen
Mitte
Monat
Moral
Motiv
Motor
Motto
Musik
Möbel
Nacht
Nagel
Namen
Natur
Nazis
Nebel
Netze
Noten
Ohren
Onkel
Opfer
Orden
Organ
Orten
Oskar
Osten
Paare
Paket
Panik
Papst
Paris
Parks
Party
Pause
Pence
Peter
Petra
Pferd
Pfund
Phase
Pilot
Platz
Polen
Preis
Prinz
Probe
Profi
Prosa
Punkt
Quote
Rache
Radio
Rande
Rasen
Ratte
Rauch
Regel
Regen
Regie
Reihe
Rente
Reste
Rhein
Rolle
Roman
Route
Rover
Rubel
Russe
Räder
Römer
Sache
Sauna
Schau
Schoß
Seele
Segen
Seite
Sekte
Senat
Seoul
Serie
Shell
Sicht
Simon
Sinne
Smith
Songs
Sonne
Sound
Spiel
Sport
Spree
Staat
Stadt
Stamm
Stars
Start
Stasi
Staub
Staus
Stein
Stern
Stirn
Stock
Stoff
Story
Strom
Stufe
Stuhl
Sturm
Sturz
Stück
Summe
Szene
Sätze
Söhne
Süden
Tagen
Tages
Tante
Tarif
Teams
Tempo
Tenor
Tests
Texas
Texte
Thema
These
Thron
Tiefs
Tiere
Tiger
Tisch
Titel
Todes
Tokio
Tonne
Toren
Traum
Treff
Trend
Trick
Trios
Trost
Typen
Täter
Tönen
Türen
Türke
UdSSR
Uhren
Ulmer
Umbau
Umzug
Union
Unmut
Vater
Vetos
Video
Villa
Viren
Virus
Vogts
Votum
Vögel
Waffe
Weber
Weile
Welle
Werke
Werks
Wesen
Wiese
Wille
Willi
Wirte
Wirts
Witwe
Witze
Woche
Wolke
World
Worte
Wände
Wüste
Zaire
Zeile
Zelle
Zeugs
Ziels
Zitat
Zorns
Zunge
Zweck
Zähne
Zügen
ahnen
ahnst
aktiv
allem
allen
aller
alles
allzu
altem
alten
altes
angab
armen
außen
außer
bauen
baute
bekam
berge
berät
besaß
bezog
biete
birgt
blaue
blieb
blind
bloße
boten
brach
breit
bunte
bösen
dabei
dafür
dahin
damit
darin
davor
deine
denen
denke
denkt
derer
desto
dicht
dicke
dient
diese
drauf
dreht
drohe
droht
dürfe
ebene
echte
ehren
eigen
einem
einen
einer
eines
einig
einst
eisig
elend
enden
endet
engem
engen
enger
enges
enorm
erben
ergab
erhob
ernte
etwas
ewige
exakt
extra
fehle
fehlt
feine
feste
finde
folgt
frage
fragt
freie
fremd
freut
frühe
fähig
fällt
fängt
fügte
fühle
fühlt
führe
führt
füllt
gaben
ganze
gegen
gelbe
gelte
gemäß
genau
genug
gerne
getan
geübt
ginge
gingt
glatt
graue
große
gutem
guten
haben
halbe
halte
heben
hebst
hefte
heiße
heißt
helle
heran
herum
heuer
heute
hielt
hilft
hinzu
hoffe
hofft
hohem
hohes
holen
holte
hängt
höher
hören
hörte
ideal
ihnen
ihrem
ihren
ihrer
ihres
immer
indem
indes
innen
jedem
jeden
jeder
jedes
jeher
jenem
jenen
jener
jenes
jetzt
junge
kalte
kauft
kehrt
keine
kenne
kennt
klagt
klang
klare
klein
knapp
komme
kommt
krank
kämen
könne
lacht
laden
lagen
lasse
leben
lebte
leere
legte
lehnt
lehrt
leise
lernt
lesen
liebe
liebt
liege
liegt
liest
ließe
links
lobte
lockt
lohnt
lässt
läuft
lösen
löste
mache
manch
meine
meint
merkt
messe
miete
minus
morde
mögen
müsse
nahen
neben
nebst
nehme
nennt
nette
neuem
neuen
neuer
neues
nicht
nimmt
näher
nötig
nützt
obere
offen
ordne
packe
packt
parat
passe
passt
plane
plant
prüfe
prüft
quasi
rasch
raten
reale
recht
reden
redet
reich
reine
reise
reist
rollt
rotem
roten
roter
rotes
rufen
ruhen
ruhig
runde
räume
räumt
rückt
sagen
sagte
sahen
sanft
satte
sauer
saßen
schuf
schön
sechs
sehen
seien
seine
sende
setze
setzt
siege
siehe
sieht
singt
sinkt
sitze
sitzt
sogar
solch
solle
sollt
somit
sonst
sorge
sorgt
sowie
spare
spart
spitz
späte
spürt
stahl
stand
starb
stark
statt
stehe
steht
stets
stich
stieg
stieß
still
stolz
stoße
stört
stößt
suche
sucht
tanze
tanzt
taten
teile
teils
teilt
teuer
teure
tiefe
tolle
total
totem
toter
totes
traue
traut
treue
trieb
trink
tritt
trotz
träge
trägt
tätig
töten
tötet
unser
unten
unter
vagem
vagen
vager
vages
viele
volle
vorab
voran
vorne
wagen
wahre
waren
warnt
warst
warte
warum
wecke
weckt
weder
wegen
wehre
wehrt
weich
weist
weite
weiße
welch
wende
wenig
werbe
werde
werte
wider
widme
wiegt
wilde
wirbt
wirft
wirke
wirkt
wisse
wobei
wofür
woher
wohin
wohne
wohnt
wolle
womit
wovon
wuchs
wunde
wurde
wähle
wählt
wärme
zahle
zeige
zeigt
zeuge
zieht
ziele
zielt
zivil
zogen
zudem
zumal
zuvor
zwang
zwölf
zählt
zügig
Ämter
Ärger
Ärzte
Übung
älter
öffne
öfter
übrig
übten
1 Abbau
2 Abend
3 Abzug
4 Adler
5 Adolf
6 Agent
7 Akten
8 Aktie
9 Alarm
10 Album
11 Allee
12 Alpen
13 Ampel
14 Amtes
15 Anbau
16 Angst
17 Anruf
18 Anton
19 Anzug
20 Apple
21 April
22 Areal
23 Arena
24 Armee
25 Armut
26 Arten
27 Asien
28 Athen
29 Augen
30 Autor
31 Autos
32 Baden
33 Bande
34 Bands
35 Basel
36 Basis
37 Bauch
38 Bauer
39 Bayer
40 Beate
41 Beide
42 Beine
43 Beleg
44 Bernd
45 Beruf
46 Beste
47 Beton
48 Beute
49 Bevor
50 Bezug
51 Bibel
52 Bitte
53 Blatt
54 Blick
55 Block
56 Blues
57 Bogen
58 Bombe
59 Boris
60 Brand
61 Braun
62 Brief
63 Bruch
64 Bruno
65 Brust
66 Buben
67 Buche
68 Busse
69 Bäume
70 Böden
71 Börse
72 Bühne
73 Büros
74 Chaos
75 Chefs
76 Chile
77 China
78 Clubs
79 Coach
80 Daher
81 Damen
82 Daran
83 Darum
84 Datei
85 Daten
86 Datum
87 Dauer
88 David
89 Davon
90 Decke
91 Deren
92 Diana
93 Dinge
94 Donau
95 Doris
96 Drama
97 Dreck
98 Druck
99 Duell
100 Durch
101 Ecken
102 Edith
103 Eiern
104 Eisen
105 Elite
106 Engel
107 Enkel
108 Erbin
109 Erich
110 Erika
111 Erlös
112 Ernst
113 Erste
114 Erwin
115 Essen
116 Ethik
117 Fahne
118 Fahrt
119 Falls
120 Farbe
121 Faust
122 Fazit
123 Feier
124 Feind
125 Felix
126 Ferne
127 Feuer
128 Figur
129 Filme
130 Films
131 Firma
132 Fisch
133 Flüge
134 Focus
135 Folge
136 Fonds
137 Forum
138 Fotos
139 Foyer
140 Franc
141 Frank
142 Franz
143 Frist
144 Fritz
145 Front
146 Frust
147 Fuchs
148 Fulda
149 Fälle
150 Fülle
151 Füßen
152 Geben
153 Gebot
154 Gehen
155 Gehör
156 Geist
157 Georg
158 Gerda
159 Gerät
160 Geste
161 Glanz
162 Glück
163 Gnade
164 Gramm
165 Griff
166 Group
167 Grund
168 Grüne
169 Gunst
170 Gutes
171 Gäste
172 Güter
173 Haare
174 Haars
175 Hafen
176 Halle
177 Hanau
178 Handy
179 Harry
180 Hauch
181 Hause
182 Hebel
183 Heike
184 Heims
185 Heinz
186 Helga
187 Henry
188 Herrn
189 Hilfe
190 Hitze
191 Horst
192 Hosen
193 Hotel
194 Huber
195 Humor
196 Hunde
197 Hände
198 Härte
199 Hätte
200 Höhen
201 Hölle
202 Hörer
203 Hügel
204 Hürde
205 Hütte
206 Ideen
207 Image
208 Index
209 Indiz
210 Infos
211 Insel
212 Intel
213 Irene
214 Islam
215 Jahre
216 Jakob
217 James
218 Japan
219 Jeans
220 Jesus
221 Josef
222 Jubel
223 Juden
224 Julia
225 Jungs
226 Jutta
227 Jäger
228 Kabel
229 Kabul
230 Kader
231 Kairo
232 Kampf
233 Kanal
234 Karin
235 Karte
236 Kasse
237 Kenia
238 Kette
239 Kinos
240 Kirch
241 Klage
242 Klaus
243 Klima
244 Klubs
245 Kohle
246 Kohls
247 Konto
248 Kopie
249 Korea
250 Kraft
251 Krebs
252 Kreis
253 Kreml
254 Kreuz
255 Krieg
256 Kripo
257 Krise
258 Krone
259 Krupp
260 Kugel
261 Kunde
262 Kunst
263 Kurse
264 Kurve
265 König
266 Köpfe
267 Küche
268 Kürze
269 Küste
270 Labor
271 Lager
272 Laien
273 Lande
274 Lange
275 Laufe
276 Laune
277 Leder
278 Legen
279 Lehre
280 Leser
281 Leute
282 Licht
283 Linie
284 Linke
285 Liste
286 Liter
287 Logik
288 Lokal
289 Luxus
290 Lyrik
291 Löhne
292 Löwen
293 Lücke
294 Macht
295 Mafia
296 Magen
297 Mainz
298 Major
299 Maler
300 Maria
301 Marie
302 Mario
303 Marke
304 Markt
305 Maske
306 Masse
307 Match
308 Mauer
309 Media
310 Meist
311 Menge
312 Meter
313 Metro
314 Meyer
315 Miami
316 Milch
317 Minen
318 Mitte
319 Monat
320 Moral
321 Motiv
322 Motor
323 Motto
324 Musik
325 Möbel
326 Nacht
327 Nagel
328 Namen
329 Natur
330 Nazis
331 Nebel
332 Netze
333 Noten
334 Ohren
335 Onkel
336 Opfer
337 Orden
338 Organ
339 Orten
340 Oskar
341 Osten
342 Paare
343 Paket
344 Panik
345 Papst
346 Paris
347 Parks
348 Party
349 Pause
350 Pence
351 Peter
352 Petra
353 Pferd
354 Pfund
355 Phase
356 Pilot
357 Platz
358 Polen
359 Preis
360 Prinz
361 Probe
362 Profi
363 Prosa
364 Punkt
365 Quote
366 Rache
367 Radio
368 Rande
369 Rasen
370 Ratte
371 Rauch
372 Regel
373 Regen
374 Regie
375 Reihe
376 Rente
377 Reste
378 Rhein
379 Rolle
380 Roman
381 Route
382 Rover
383 Rubel
384 Russe
385 Räder
386 Römer
387 Sache
388 Sauna
389 Schau
390 Schoß
391 Seele
392 Segen
393 Seite
394 Sekte
395 Senat
396 Seoul
397 Serie
398 Shell
399 Sicht
400 Simon
401 Sinne
402 Smith
403 Songs
404 Sonne
405 Sound
406 Spiel
407 Sport
408 Spree
409 Staat
410 Stadt
411 Stamm
412 Stars
413 Start
414 Stasi
415 Staub
416 Staus
417 Stein
418 Stern
419 Stirn
420 Stock
421 Stoff
422 Story
423 Strom
424 Stufe
425 Stuhl
426 Sturm
427 Sturz
428 Stück
429 Summe
430 Szene
431 Sätze
432 Söhne
433 Süden
434 Tagen
435 Tages
436 Tante
437 Tarif
438 Teams
439 Tempo
440 Tenor
441 Tests
442 Texas
443 Texte
444 Thema
445 These
446 Thron
447 Tiefs
448 Tiere
449 Tiger
450 Tisch
451 Titel
452 Todes
453 Tokio
454 Tonne
455 Toren
456 Traum
457 Treff
458 Trend
459 Trick
460 Trios
461 Trost
462 Typen
463 Täter
464 Tönen
465 Türen
466 Türke
467 UdSSR
468 Uhren
469 Ulmer
470 Umbau
471 Umzug
472 Union
473 Unmut
474 Vater
475 Vetos
476 Video
477 Villa
478 Viren
479 Virus
480 Vogts
481 Votum
482 Vögel
483 Waffe
484 Weber
485 Weile
486 Welle
487 Werke
488 Werks
489 Wesen
490 Wiese
491 Wille
492 Willi
493 Wirte
494 Wirts
495 Witwe
496 Witze
497 Woche
498 Wolke
499 World
500 Worte
501 Wände
502 Wüste
503 Zaire
504 Zeile
505 Zelle
506 Zeugs
507 Ziels
508 Zitat
509 Zorns
510 Zunge
511 Zweck
512 Zähne
513 Zügen
514 ahnen
515 ahnst
516 aktiv
517 allem
518 allen
519 aller
520 alles
521 allzu
522 altem
523 alten
524 altes
525 angab
526 armen
527 außen
528 außer
529 bauen
530 baute
531 bekam
532 berge
533 berät
534 besaß
535 bezog
536 biete
537 birgt
538 blaue
539 blieb
540 blind
541 bloße
542 boten
543 brach
544 breit
545 bunte
546 bösen
547 dabei
548 dafür
549 dahin
550 damit
551 darin
552 davor
553 deine
554 denen
555 denke
556 denkt
557 derer
558 desto
559 dicht
560 dicke
561 dient
562 diese
563 drauf
564 dreht
565 drohe
566 droht
567 dürfe
568 ebene
569 echte
570 ehren
571 eigen
572 einem
573 einen
574 einer
575 eines
576 einig
577 einst
578 eisig
579 elend
580 enden
581 endet
582 engem
583 engen
584 enger
585 enges
586 enorm
587 erben
588 ergab
589 erhob
590 ernte
591 etwas
592 ewige
593 exakt
594 extra
595 fehle
596 fehlt
597 feine
598 feste
599 finde
600 folgt
601 frage
602 fragt
603 freie
604 fremd
605 freut
606 frühe
607 fähig
608 fällt
609 fängt
610 fügte
611 fühle
612 fühlt
613 führe
614 führt
615 füllt
616 gaben
617 ganze
618 gegen
619 gelbe
620 gelte
621 gemäß
622 genau
623 genug
624 gerne
625 getan
626 geübt
627 ginge
628 gingt
629 glatt
630 graue
631 große
632 gutem
633 guten
634 haben
635 halbe
636 halte
637 heben
638 hebst
639 hefte
640 heiße
641 heißt
642 helle
643 heran
644 herum
645 heuer
646 heute
647 hielt
648 hilft
649 hinzu
650 hoffe
651 hofft
652 hohem
653 hohes
654 holen
655 holte
656 hängt
657 höher
658 hören
659 hörte
660 ideal
661 ihnen
662 ihrem
663 ihren
664 ihrer
665 ihres
666 immer
667 indem
668 indes
669 innen
670 jedem
671 jeden
672 jeder
673 jedes
674 jeher
675 jenem
676 jenen
677 jener
678 jenes
679 jetzt
680 junge
681 kalte
682 kauft
683 kehrt
684 keine
685 kenne
686 kennt
687 klagt
688 klang
689 klare
690 klein
691 knapp
692 komme
693 kommt
694 krank
695 kämen
696 könne
697 lacht
698 laden
699 lagen
700 lasse
701 leben
702 lebte
703 leere
704 legte
705 lehnt
706 lehrt
707 leise
708 lernt
709 lesen
710 liebe
711 liebt
712 liege
713 liegt
714 liest
715 ließe
716 links
717 lobte
718 lockt
719 lohnt
720 lässt
721 läuft
722 lösen
723 löste
724 mache
725 manch
726 meine
727 meint
728 merkt
729 messe
730 miete
731 minus
732 morde
733 mögen
734 müsse
735 nahen
736 neben
737 nebst
738 nehme
739 nennt
740 nette
741 neuem
742 neuen
743 neuer
744 neues
745 nicht
746 nimmt
747 näher
748 nötig
749 nützt
750 obere
751 offen
752 ordne
753 packe
754 packt
755 parat
756 passe
757 passt
758 plane
759 plant
760 prüfe
761 prüft
762 quasi
763 rasch
764 raten
765 reale
766 recht
767 reden
768 redet
769 reich
770 reine
771 reise
772 reist
773 rollt
774 rotem
775 roten
776 roter
777 rotes
778 rufen
779 ruhen
780 ruhig
781 runde
782 räume
783 räumt
784 rückt
785 sagen
786 sagte
787 sahen
788 sanft
789 satte
790 sauer
791 saßen
792 schuf
793 schön
794 sechs
795 sehen
796 seien
797 seine
798 sende
799 setze
800 setzt
801 siege
802 siehe
803 sieht
804 singt
805 sinkt
806 sitze
807 sitzt
808 sogar
809 solch
810 solle
811 sollt
812 somit
813 sonst
814 sorge
815 sorgt
816 sowie
817 spare
818 spart
819 spitz
820 späte
821 spürt
822 stahl
823 stand
824 starb
825 stark
826 statt
827 stehe
828 steht
829 stets
830 stich
831 stieg
832 stieß
833 still
834 stolz
835 stoße
836 stört
837 stößt
838 suche
839 sucht
840 tanze
841 tanzt
842 taten
843 teile
844 teils
845 teilt
846 teuer
847 teure
848 tiefe
849 tolle
850 total
851 totem
852 toter
853 totes
854 traue
855 traut
856 treue
857 trieb
858 trink
859 tritt
860 trotz
861 träge
862 trägt
863 tätig
864 töten
865 tötet
866 unser
867 unten
868 unter
869 vagem
870 vagen
871 vager
872 vages
873 viele
874 volle
875 vorab
876 voran
877 vorne
878 wagen
879 wahre
880 waren
881 warnt
882 warst
883 warte
884 warum
885 wecke
886 weckt
887 weder
888 wegen
889 wehre
890 wehrt
891 weich
892 weist
893 weite
894 weiße
895 welch
896 wende
897 wenig
898 werbe
899 werde
900 werte
901 wider
902 widme
903 wiegt
904 wilde
905 wirbt
906 wirft
907 wirke
908 wirkt
909 wisse
910 wobei
911 wofür
912 woher
913 wohin
914 wohne
915 wohnt
916 wolle
917 womit
918 wovon
919 wuchs
920 wunde
921 wurde
922 wähle
923 wählt
924 wärme
925 zahle
926 zeige
927 zeigt
928 zeuge
929 zieht
930 ziele
931 zielt
932 zivil
933 zogen
934 zudem
935 zumal
936 zuvor
937 zwang
938 zwölf
939 zählt
940 zügig
941 Ämter
942 Ärger
943 Ärzte
944 Übung
945 älter
946 öffne
947 öfter
948 übrig
949 übten

View File

@@ -147,6 +147,36 @@ class ColorTest {
@Test @Test
void parseHexcode() { void parseHexcode() {
Color c;
c = Color.parseHexcode("#f05025");
assertEquals(Color.RED, c);
c = Color.parseHexcode("#F8EF22");
assertEquals(Color.YELLOW, c);
c = Color.parseHexcode("#FF40ff");
assertEquals(new Color(255, 64, 255), c);
// TODO: Test hexcodes of length 3, 6 and 9
// TODO: Test malformed hexcodes
}
@Test
void parseString() {
Color c;
c = Color.parseString("red");
assertEquals(new Color(255, 0, 0), c);
c = Color.parseString("zombie green");
assertEquals(Color.parseHexcode("#54c571"), c);
c = Color.parseString("algae green");
assertEquals(new Color(100, 233, 134), c);
c = Color.parseString("light slate gray");
assertEquals(Color.parseHexcode("#6d7b8d"), c);
} }
@Test @Test
@@ -173,10 +203,6 @@ class ColorTest {
void HSLtoRGB() { void HSLtoRGB() {
} }
@Test
void testHSLtoRGB() {
}
@Test @Test
void copy() { void copy() {
} }

View File

@@ -25,10 +25,28 @@ class ConstantsTest {
assertEquals(5.0, Constants.max(new double[]{ 5.0, 5.0, 5.0, 5.0, 5.0 }), 0.000001); assertEquals(5.0, Constants.max(new double[]{ 5.0, 5.0, 5.0, 5.0, 5.0 }), 0.000001);
assertEquals(5.0, Constants.max(new double[]{ 5.0 }), 0.000001); assertEquals(5.0, Constants.max(new double[]{ 5.0 }), 0.000001);
assertThrows(IllegalArgumentException.class, () -> Constants.max(null)); double[] doubleArr = null;
assertThrows(IllegalArgumentException.class, () -> Constants.max(doubleArr));
assertThrows(IllegalArgumentException.class, () -> Constants.max(new double[]{})); assertThrows(IllegalArgumentException.class, () -> Constants.max(new double[]{}));
} }
@Test
void maxInt() {
assertEquals(5, Constants.max(5, 0));
assertEquals(5, Constants.max(5, 4));
assertEquals(5, Constants.max(5, 5));
assertEquals(100, Constants.max(100, -100));
assertEquals(0, Constants.max(0, 0));
assertEquals(5, Constants.max(new double[]{ 0, 1, 2, 3, 4, 5 }));
assertEquals(5, Constants.max(new double[]{ 5, 5, 5, 5, 5 }));
assertEquals(5, Constants.max(new double[]{ 5 }));
int[] intArr = null;
assertThrows(IllegalArgumentException.class, () -> Constants.max(intArr));
assertThrows(IllegalArgumentException.class, () -> Constants.max(new int[]{}));
}
@Test @Test
void abs() { void abs() {
assertEquals(5.0, Constants.abs(5.0), 0.000001); assertEquals(5.0, Constants.abs(5.0), 0.000001);

View File

@@ -5,7 +5,8 @@ import schule.ngb.zm.util.io.FileLoader;
import java.util.List; import java.util.List;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
class FileLoaderTest { class FileLoaderTest {