Ein wenig an der GUI gebastetl

This commit is contained in:
2026-08-19 22:21:32 +02:00
parent c221c26a23
commit 974ee5765e
4 changed files with 132 additions and 13 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@@ -11326,8 +11326,10 @@ public class EditorApp extends Application {
var clip = clipCombo.getValue(); var clip = clipCombo.getValue();
if (action != null && clip != null) { if (action != null && clip != null) {
String entry = action.name() + "" + clip; String entry = action.name() + "" + clip;
if (!animSetActionListView.getItems().contains(entry)) if (!animSetActionListView.getItems().contains(entry)) {
animSetActionListView.getItems().add(entry); animSetActionListView.getItems().add(entry);
autoSaveAnimSet();
}
} }
}); });
} }

View File

@@ -18,6 +18,12 @@ import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry; import com.jme3.scene.Geometry;
import com.jme3.scene.Node; import com.jme3.scene.Node;
import com.jme3.scene.shape.Quad; import com.jme3.scene.shape.Quad;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.image.ColorSpace;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import de.blight.game.post.GaussianBlurFilter; import de.blight.game.post.GaussianBlurFilter;
import de.blight.game.scene.WorldScene; import de.blight.game.scene.WorldScene;
@@ -65,8 +71,15 @@ public abstract class MenuScreen extends BaseAppState {
private final String MAP_CLICK = "_MsClick_" + System.identityHashCode(this); private final String MAP_CLICK = "_MsClick_" + System.identityHashCode(this);
private record Btn(float x, float y, float w, float h, Runnable action) {} private record Btn(float x, float y, float w, float h, Runnable action,
Node patchNode, BitmapText label) {}
private final List<Btn> buttons = new ArrayList<>(); private final List<Btn> buttons = new ArrayList<>();
private Btn pressedBtn = null;
private Btn hoveredBtn = null;
private Node hoverGlow = null;
private Texture2D glowTex = null;
private static final float PRESS_DX = 1f, PRESS_DY = -2f;
private static final float GLOW_PAD = 20f;
// ── Lifecycle ───────────────────────────────────────────────────────────── // ── Lifecycle ─────────────────────────────────────────────────────────────
@@ -114,6 +127,9 @@ public abstract class MenuScreen extends BaseAppState {
if (canvasNode != null) { guiNode.detachChild(canvasNode); canvasNode = null; } if (canvasNode != null) { guiNode.detachChild(canvasNode); canvasNode = null; }
panel = null; panel = null;
buttons.clear(); buttons.clear();
pressedBtn = null;
hoveredBtn = null;
if (hoverGlow != null) { hoverGlow.removeFromParent(); hoverGlow = null; }
if (withBlur()) removeBlur(); if (withBlur()) removeBlur();
} }
@@ -171,7 +187,8 @@ public abstract class MenuScreen extends BaseAppState {
case ARROW -> NinePatch.buttonArrow(assets); case ARROW -> NinePatch.buttonArrow(assets);
default -> NinePatch.button(assets); default -> NinePatch.button(assets);
}; };
parent.attachChild(patch.build(x, y, w, h, 0f)); Node patchNode = patch.build(x, y, w, h, 0f);
parent.attachChild(patchNode);
ColorRGBA col = (style == BtnStyle.DISABLED) ? UiTheme.COL_MUTED : UiTheme.COL_WHITE; ColorRGBA col = (style == BtnStyle.DISABLED) ? UiTheme.COL_MUTED : UiTheme.COL_WHITE;
BitmapText lbl = crispText(label, UiTheme.FONT_LABEL, col); BitmapText lbl = crispText(label, UiTheme.FONT_LABEL, col);
@@ -180,21 +197,108 @@ public abstract class MenuScreen extends BaseAppState {
parent.attachChild(lbl); parent.attachChild(lbl);
if (style != BtnStyle.DISABLED && action != null) { if (style != BtnStyle.DISABLED && action != null) {
buttons.add(new Btn(x, y, w, h, action)); buttons.add(new Btn(x, y, w, h, action, patchNode, lbl));
} }
} }
// ── Update-Loop (Hover) ───────────────────────────────────────────────────
@Override
public void update(float tpf) {
if (buttons.isEmpty() || panel == null) return;
float[] v = toVirtual(app.getInputManager().getCursorPosition());
Btn hovered = null;
for (Btn b : buttons) {
if (v[0] >= b.x() && v[0] <= b.x() + b.w()
&& v[1] >= b.y() && v[1] <= b.y() + b.h()) {
hovered = b;
break;
}
}
if (hovered != hoveredBtn) {
if (hoverGlow != null) { hoverGlow.removeFromParent(); hoverGlow = null; }
hoveredBtn = hovered;
if (hoveredBtn != null) {
hoverGlow = buildHoverGlow(hoveredBtn);
panel.attachChild(hoverGlow);
}
}
if (hoverGlow != null) {
float dx = (pressedBtn == hoveredBtn) ? PRESS_DX : 0f;
float dy = (pressedBtn == hoveredBtn) ? PRESS_DY : 0f;
hoverGlow.setLocalTranslation(hoveredBtn.x() + dx, hoveredBtn.y() + dy, 0f);
}
}
private Node buildHoverGlow(Btn b) {
Geometry q = new Geometry("glow",
new Quad(b.w() + GLOW_PAD * 2f, b.h() + GLOW_PAD * 2f));
Material m = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
m.setTexture("ColorMap", getGlowTex());
m.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.AlphaAdditive);
q.setQueueBucket(RenderQueue.Bucket.Gui);
q.setMaterial(m);
// -GLOW_PAD damit der Glow um den Button herausragt; Node selbst wird auf Button-Pos gesetzt
q.setLocalTranslation(-GLOW_PAD, -GLOW_PAD, -0.5f);
Node g = new Node("hover-glow");
g.attachChild(q);
return g;
}
private Texture2D getGlowTex() {
if (glowTex != null) return glowTex;
int W = 64, H = 64;
ByteBuffer buf = BufferUtils.createByteBuffer(W * H * 4);
for (int py = 0; py < H; py++) {
for (int px = 0; px < W; px++) {
float u = (px + 0.5f) / W;
float v = (py + 0.5f) / H;
float a = glowFade(u) * glowFade(v);
buf.put((byte) 13); // R ≈ 0.05 × 255
buf.put((byte) 255); // G = neon
buf.put((byte) 51); // B ≈ 0.20 × 255
buf.put((byte)(int)(a * 220f)); // A peak ≈ 86 %
}
}
buf.flip();
glowTex = new Texture2D(new Image(Image.Format.RGBA8, W, H, buf, ColorSpace.Linear));
glowTex.setMagFilter(Texture.MagFilter.Bilinear);
glowTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
return glowTex;
}
private static float glowFade(float t) {
float d = Math.abs(t - 0.5f) * 2f; // 0 = Mitte, 1 = Rand
float x = 1f - Math.min(1f, d);
return x * x * (3f - 2f * x); // smoothstep
}
// ── Klick-Listener ──────────────────────────────────────────────────────── // ── Klick-Listener ────────────────────────────────────────────────────────
private final ActionListener clickListener = (name, pressed, tpf) -> { private final ActionListener clickListener = (name, pressed, tpf) -> {
if (!pressed) return;
float[] v = toVirtual(app.getInputManager().getCursorPosition()); float[] v = toVirtual(app.getInputManager().getCursorPosition());
onScreenClick(v); if (pressed) {
for (Btn b : buttons) { onScreenClick(v);
if (v[0] >= b.x() && v[0] <= b.x() + b.w() for (Btn b : buttons) {
&& v[1] >= b.y() && v[1] <= b.y() + b.h()) { if (v[0] >= b.x() && v[0] <= b.x() + b.w()
b.action().run(); && v[1] >= b.y() && v[1] <= b.y() + b.h()) {
return; pressedBtn = b;
b.patchNode().move(PRESS_DX, PRESS_DY, 0f);
b.label().move(PRESS_DX, PRESS_DY, 0f);
return;
}
}
} else {
if (pressedBtn != null) {
pressedBtn.patchNode().move(-PRESS_DX, -PRESS_DY, 0f);
pressedBtn.label().move(-PRESS_DX, -PRESS_DY, 0f);
Btn released = pressedBtn;
pressedBtn = null;
// Aktion nur auslösen wenn Maus noch über dem Button
if (v[0] >= released.x() && v[0] <= released.x() + released.w()
&& v[1] >= released.y() && v[1] <= released.y() + released.h()) {
released.action().run();
}
} }
} }
}; };

View File

@@ -171,6 +171,12 @@ public class NinePatch {
int t = ty >= 0 ? ty + 1 : h / 4; int t = ty >= 0 ? ty + 1 : h / 4;
int b = by >= 0 ? h - by : h / 4; int b = by >= 0 ? h - by : h / 4;
// Sicherstellen, dass Mittelbereich mindestens 1 Pixel groß ist.
// Fehlerhafte Guide-Erkennung (Pixel mit zufällig passendem Alpha) kann
// sonst l+r >= w oder t+b >= h erzeugen → Endlosschleife in den Tiling-Methoden.
if (l + r >= w) { l = w / 4; r = w / 4; }
if (t + b >= h) { t = h / 4; b = h / 4; }
return new int[]{l, r, t, b}; return new int[]{l, r, t, b};
} }
@@ -182,12 +188,16 @@ public class NinePatch {
* ty, by: Zeilenindizes der oberen/unteren Guide-Linie (PNG-Koordinaten) * ty, by: Zeilenindizes der oberen/unteren Guide-Linie (PNG-Koordinaten)
*/ */
private static void stripGuides(BufferedImage img, int w, int h, int lx, int rx, int ty, int by) { private static void stripGuides(BufferedImage img, int w, int h, int lx, int rx, int ty, int by) {
// Snapshot der Guide-Positionen (vor Modifikation) // Snapshot der Guide-Positionen (vor Modifikation).
// Als Guide zählen: exakte Guide-Alphas ODER semi-transparente Pixel (alpha < 200)
// auf den bekannten Guide-Zeilen/-Spalten — entsteht durch Anti-Aliasing beim Malen.
boolean[][] isGuide = new boolean[h][w]; boolean[][] isGuide = new boolean[h][w];
for (int y = 0; y < h; y++) { for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) { for (int x = 0; x < w; x++) {
int a = (img.getRGB(x, y) >> 24) & 0xff; int a = (img.getRGB(x, y) >> 24) & 0xff;
isGuide[y][x] = GUIDE_ALPHAS.contains(a); boolean exactGuide = GUIDE_ALPHAS.contains(a);
boolean onGuideLine = (x == lx || x == rx || y == ty || y == by);
isGuide[y][x] = exactGuide || (onGuideLine && a < 200 && a > 0);
} }
} }
@@ -332,6 +342,7 @@ public class NinePatch {
float u1, float v1, float u2, float v2, float u1, float v1, float u2, float v2,
float cw, float ch, float cw, float ch,
float texW, float texH, float z) { float texW, float texH, float z) {
if (cw <= 0 || ch <= 0) return;
int iz = 0; int iz = 0;
for (float py = sy1; py < sy2; py += ch, iz++) { for (float py = sy1; py < sy2; py += ch, iz++) {
float th = Math.min(ch, sy2 - py); float th = Math.min(ch, sy2 - py);
@@ -352,6 +363,7 @@ public class NinePatch {
float sx1, float py, float sx2, float ph, float sx1, float py, float sx2, float ph,
float u1, float u2, float vMin, float vMax, float u1, float u2, float vMin, float vMax,
float cw, float texW, float z) { float cw, float texW, float z) {
if (cw <= 0) return;
int ix = 0; int ix = 0;
for (float px = sx1; px < sx2; px += cw, ix++) { for (float px = sx1; px < sx2; px += cw, ix++) {
float tw = Math.min(cw, sx2 - px); float tw = Math.min(cw, sx2 - px);
@@ -366,6 +378,7 @@ public class NinePatch {
float px, float sy1, float pw, float sy2, float px, float sy1, float pw, float sy2,
float uMin, float uMax, float v1, float v2, float uMin, float uMax, float v1, float v2,
float ch, float texH, float z) { float ch, float texH, float z) {
if (ch <= 0) return;
int iz = 0; int iz = 0;
for (float py = sy1; py < sy2; py += ch, iz++) { for (float py = sy1; py < sy2; py += ch, iz++) {
float th = Math.min(ch, sy2 - py); float th = Math.min(ch, sy2 - py);