Menü Buttons angepasst, Screenshot Funktion optimiert

This commit is contained in:
2026-08-17 12:41:21 +02:00
parent ee8c235655
commit c09502ad3a
9 changed files with 131 additions and 15 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1477,6 +1477,7 @@ public class EditorApp extends Application {
saveItem.setAccelerator(javafx.scene.input.KeyCombination.keyCombination("Ctrl+S")); saveItem.setAccelerator(javafx.scene.input.KeyCombination.keyCombination("Ctrl+S"));
saveItem.setOnAction(e -> { input.saveRequested = true; setStatus("Speichern…"); }); saveItem.setOnAction(e -> { input.saveRequested = true; setStatus("Speichern…"); });
MenuItem screenshotItem = new MenuItem("Screenshot"); MenuItem screenshotItem = new MenuItem("Screenshot");
screenshotItem.setAccelerator(javafx.scene.input.KeyCombination.keyCombination("Ctrl+F12"));
screenshotItem.setOnAction(e -> takeEditorScreenshot()); screenshotItem.setOnAction(e -> takeEditorScreenshot());
MenuItem settingsItem = new MenuItem("Einstellungen…"); MenuItem settingsItem = new MenuItem("Einstellungen…");
settingsItem.setOnAction(e -> openSettings()); settingsItem.setOnAction(e -> openSettings());
@@ -6442,6 +6443,7 @@ public class EditorApp extends Application {
} }
private void takeEditorScreenshot() { private void takeEditorScreenshot() {
playScreenshotClick();
WritableImage image = primaryStage.getScene().snapshot(null); WritableImage image = primaryStage.getScene().snapshot(null);
String timestamp = java.time.LocalDateTime.now() String timestamp = java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")); .format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"));
@@ -6456,6 +6458,34 @@ public class EditorApp extends Application {
} }
} }
private void playScreenshotClick() {
Thread t = new Thread(() -> {
try {
int sr = 44100;
int frames = (int) (sr * 0.08);
byte[] buf = new byte[frames * 2];
java.util.Random rng = new java.util.Random();
for (int i = 0; i < frames; i++) {
double env = Math.exp(-i / (sr * 0.012));
double s = (rng.nextGaussian() * 0.55 + Math.sin(2 * Math.PI * 3200.0 * i / sr) * 0.45) * env * 0.85;
short val = (short) Math.max(-32767, Math.min(32767, s * 32767));
buf[i * 2] = (byte) (val & 0xff);
buf[i * 2 + 1] = (byte) ((val >> 8) & 0xff);
}
javax.sound.sampled.AudioFormat fmt = new javax.sound.sampled.AudioFormat(sr, 16, 1, true, false);
javax.sound.sampled.DataLine.Info info = new javax.sound.sampled.DataLine.Info(javax.sound.sampled.SourceDataLine.class, fmt);
javax.sound.sampled.SourceDataLine line = (javax.sound.sampled.SourceDataLine) javax.sound.sampled.AudioSystem.getLine(info);
line.open(fmt);
line.start();
line.write(buf, 0, buf.length);
line.drain();
line.close();
} catch (Exception ignored) {}
}, "screenshot-click");
t.setDaemon(true);
t.start();
}
private void handleTextureImport(javafx.stage.Window owner) { private void handleTextureImport(javafx.stage.Window owner) {
FileChooser fc = new FileChooser(); FileChooser fc = new FileChooser();
fc.setTitle("Texturen importieren (nicht-PNG wird automatisch konvertiert)"); fc.setTitle("Texturen importieren (nicht-PNG wird automatisch konvertiert)");

View File

@@ -50,6 +50,7 @@ public class BlightGame extends SimpleApplication {
private AudioSettings audioSettings; private AudioSettings audioSettings;
private ScreenshotAppState screenshotState; private ScreenshotAppState screenshotState;
private Path screenshotDir; private Path screenshotDir;
private com.jme3.audio.AudioNode screenshotClickSound;
private WorldScene worldScene; private WorldScene worldScene;
private ConfigScreen configScreen; private ConfigScreen configScreen;
private GraphicsScreen graphicsScreen; private GraphicsScreen graphicsScreen;
@@ -380,8 +381,12 @@ public class BlightGame extends SimpleApplication {
try { try {
screenshotDir = BlightHome.resolve("screenshots"); screenshotDir = BlightHome.resolve("screenshots");
Files.createDirectories(screenshotDir); Files.createDirectories(screenshotDir);
screenshotState = new ScreenshotAppState(screenshotDir + File.separator, "screenshot"); screenshotState = new ScreenshotAppState("", "screenshot");
stateManager.attach(screenshotState); stateManager.attach(screenshotState);
screenshotClickSound = new com.jme3.audio.AudioNode(assetManager, "audio/static/screenshot_click.ogg", com.jme3.audio.AudioData.DataType.Buffer);
screenshotClickSound.setPositional(false);
screenshotClickSound.setLooping(false);
screenshotClickSound.setVolume(1f);
log.info("Screenshots werden gespeichert in: {}", screenshotDir.toAbsolutePath()); log.info("Screenshots werden gespeichert in: {}", screenshotDir.toAbsolutePath());
} catch (IOException e) { } catch (IOException e) {
log.warn("Screenshot-Verzeichnis konnte nicht angelegt werden", e); log.warn("Screenshot-Verzeichnis konnte nicht angelegt werden", e);
@@ -389,8 +394,12 @@ public class BlightGame extends SimpleApplication {
inputManager.addMapping("Screenshot", new KeyTrigger(KeyInput.KEY_F12)); inputManager.addMapping("Screenshot", new KeyTrigger(KeyInput.KEY_F12));
inputManager.addListener((ActionListener) (name, isPressed, tpf) -> { inputManager.addListener((ActionListener) (name, isPressed, tpf) -> {
if (isPressed && screenshotState != null) { if (isPressed && screenshotState != null) {
log.info("[Screenshot] Speichere in: {}", screenshotDir.toAbsolutePath()); String ts = java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"));
screenshotState.setFileName(screenshotDir + java.io.File.separator + "blight_" + ts + "_");
log.info("[Screenshot] Speichere: blight_{}.png", ts);
screenshotState.takeScreenshot(); screenshotState.takeScreenshot();
if (screenshotClickSound != null) screenshotClickSound.playInstance();
} }
}, "Screenshot"); }, "Screenshot");

View File

@@ -16,6 +16,8 @@ import com.jme3.texture.image.ColorSpace;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@@ -127,6 +129,8 @@ public class NinePatch {
// Guide-Pixel durch Rahmen-Nachbarfarbe ersetzen // Guide-Pixel durch Rahmen-Nachbarfarbe ersetzen
stripGuides(img, w, h, l - 1, w - r, t - 1, h - b); stripGuides(img, w, h, l - 1, w - r, t - 1, h - b);
img = gaussianBlur(img, 0.0f);
Texture2D tex = toJmeTexture(img, w, h); Texture2D tex = toJmeTexture(img, w, h);
return new NinePatch(assets, tex, w, h, l, r, t, b); return new NinePatch(assets, tex, w, h, l, r, t, b);
} }
@@ -219,6 +223,26 @@ public class NinePatch {
return 0; // Fallback: transparent return 0; // Fallback: transparent
} }
/**
* Weicher Gauss-Blur (3×3-Kernel) auf das Bild glättet Übergänge an Patch-Grenzen.
* sigma=0 deaktiviert den Blur.
*/
private static BufferedImage gaussianBlur(BufferedImage img, float sigma) {
if (sigma <= 0f) { return img; }
float s2 = 2f * sigma * sigma;
float[] data = {
(float) Math.exp(-2 / s2), (float) Math.exp(-1 / s2), (float) Math.exp(-2 / s2),
(float) Math.exp(-1 / s2), 1f, (float) Math.exp(-1 / s2),
(float) Math.exp(-2 / s2), (float) Math.exp(-1 / s2), (float) Math.exp(-2 / s2)
};
float sum = 0; for (float v : data) sum += v;
for (int i = 0; i < data.length; i++) data[i] /= sum;
ConvolveOp op = new ConvolveOp(new Kernel(3, 3, data), ConvolveOp.EDGE_NO_OP, null);
BufferedImage out = new BufferedImage(img.getWidth(), img.getHeight(), img.getType());
op.filter(img, out);
return out;
}
/** /**
* Konvertiert ein BufferedImage in eine JME3-Textur. * Konvertiert ein BufferedImage in eine JME3-Textur.
* Zeilen werden von unten nach oben gelesen (entspricht JME3-Y-Flip beim PNG-Laden). * Zeilen werden von unten nach oben gelesen (entspricht JME3-Y-Flip beim PNG-Laden).
@@ -266,7 +290,7 @@ public class NinePatch {
*/ */
public Node build(float x, float y, float w, float h, float z) { public Node build(float x, float y, float w, float h, float z) {
tex.setWrap(Texture.WrapMode.EdgeClamp); tex.setWrap(Texture.WrapMode.EdgeClamp);
tex.setMagFilter(Texture.MagFilter.Nearest); tex.setMagFilter(Texture.MagFilter.Bilinear);
Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md"); Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setTexture("ColorMap", tex); mat.setTexture("ColorMap", tex);
@@ -282,22 +306,75 @@ public class NinePatch {
float sx1 = x + l, sx2 = x + w - r; float sx1 = x + l, sx2 = x + w - r;
float sy1 = y + b, sy2 = y + h - t; float sy1 = y + b, sy2 = y + h - t;
// Kachelgröße des Mittelbereichs in Bildschirmpixeln
float cw = (u2 - u1) * texW;
float ch = (v2 - v1) * texH;
Node n = new Node("ninepatch"); Node n = new Node("ninepatch");
// Reihe unten // Reihe unten Ecken fix, untere Kante Mirror-X
n.attachChild(q(mat, x, y, l, b, 0, 0, u1, v1, z)); n.attachChild(q(mat, x, y, l, b, 0, 0, u1, v1, z));
n.attachChild(q(mat, sx1, y, sx2-sx1, b, u1, 0, u2, v1, z)); tileHEdge(n, mat, sx1, y, sx2, b, u1, u2, 0, v1, cw, texW, z);
n.attachChild(q(mat, sx2, y, r, b, u2, 0, 1, v1, z)); n.attachChild(q(mat, sx2, y, r, b, u2, 0, 1, v1, z));
// Reihe mitte // Reihe mitte Seiten Mirror-Y, Mitte Mirror-X+Y
n.attachChild(q(mat, x, sy1, l, sy2-sy1, 0, v1, u1, v2, z)); tileVEdge(n, mat, x, sy1, l, sy2, 0, u1, v1, v2, ch, texH, z);
n.attachChild(q(mat, sx1, sy1, sx2-sx1, sy2-sy1, u1, v1, u2, v2, z)); tileCenterQuads(n, mat, sx1, sy1, sx2, sy2, u1, v1, u2, v2, cw, ch, texW, texH, z);
n.attachChild(q(mat, sx2, sy1, r, sy2-sy1, u2, v1, 1, v2, z)); tileVEdge(n, mat, sx2, sy1, r, sy2, u2, 1f, v1, v2, ch, texH, z);
// Reihe oben // Reihe oben Ecken fix, obere Kante Mirror-X
n.attachChild(q(mat, x, sy2, l, t, 0, v2, u1, 1, z)); n.attachChild(q(mat, x, sy2, l, t, 0, v2, u1, 1f, z));
n.attachChild(q(mat, sx1, sy2, sx2-sx1, t, u1, v2, u2, 1, z)); tileHEdge(n, mat, sx1, sy2, sx2, t, u1, u2, v2, 1f, cw, texW, z);
n.attachChild(q(mat, sx2, sy2, r, t, u2, v2, 1, 1, z)); n.attachChild(q(mat, sx2, sy2, r, t, u2, v2, 1f, 1f, z));
return n; return n;
} }
/** Mitte: Mirror-Repeat in X und Y. */
private static void tileCenterQuads(Node n, Material mat,
float sx1, float sy1, float sx2, float sy2,
float u1, float v1, float u2, float v2,
float cw, float ch,
float texW, float texH, float z) {
int iz = 0;
for (float py = sy1; py < sy2; py += ch, iz++) {
float th = Math.min(ch, sy2 - py);
float vA = (iz & 1) == 0 ? v1 : v2;
float vB = (iz & 1) == 0 ? v1+th/texH : v2-th/texH;
int ix = 0;
for (float px = sx1; px < sx2; px += cw, ix++) {
float tw = Math.min(cw, sx2 - px);
float uA = (ix & 1) == 0 ? u1 : u2;
float uB = (ix & 1) == 0 ? u1+tw/texW : u2-tw/texW;
n.attachChild(q(mat, px, py, tw, th, uA, vA, uB, vB, z));
}
}
}
/** Horizontaler Randstreifen (oben/unten): Mirror-Repeat nur in X. */
private static void tileHEdge(Node n, Material mat,
float sx1, float py, float sx2, float ph,
float u1, float u2, float vMin, float vMax,
float cw, float texW, float z) {
int ix = 0;
for (float px = sx1; px < sx2; px += cw, ix++) {
float tw = Math.min(cw, sx2 - px);
float uA = (ix & 1) == 0 ? u1 : u2;
float uB = (ix & 1) == 0 ? u1+tw/texW : u2-tw/texW;
n.attachChild(q(mat, px, py, tw, ph, uA, vMin, uB, vMax, z));
}
}
/** Vertikaler Randstreifen (links/rechts): Mirror-Repeat nur in Y. */
private static void tileVEdge(Node n, Material mat,
float px, float sy1, float pw, float sy2,
float uMin, float uMax, float v1, float v2,
float ch, float texH, float z) {
int iz = 0;
for (float py = sy1; py < sy2; py += ch, iz++) {
float th = Math.min(ch, sy2 - py);
float vA = (iz & 1) == 0 ? v1 : v2;
float vB = (iz & 1) == 0 ? v1+th/texH : v2-th/texH;
n.attachChild(q(mat, px, py, pw, th, uMin, vA, uMax, vB, z));
}
}
private static Geometry q(Material mat, private static Geometry q(Material mat,
float x, float y, float w, float h, float x, float y, float w, float h,
float uMin, float vMin, float uMax, float vMax, float uMin, float vMin, float uMax, float vMax,