refactor menu logic

This commit is contained in:
2025-11-14 20:06:19 +01:00
parent 6868f53e38
commit edc983091b
8 changed files with 476 additions and 96 deletions

View File

@@ -14,10 +14,7 @@ import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
import com.badlogic.gdx.graphics.g3d.environment.DirectionalShadowLight; import com.badlogic.gdx.graphics.g3d.environment.DirectionalShadowLight;
import com.badlogic.gdx.graphics.g3d.utils.DepthShaderProvider; import com.badlogic.gdx.graphics.g3d.utils.DepthShaderProvider;
import com.badlogic.gdx.graphics.glutils.FrameBuffer; import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import wtf.beatrice.retrorender.engine.DebugHud; import wtf.beatrice.retrorender.engine.*;
import wtf.beatrice.retrorender.engine.FpsCameraController;
import wtf.beatrice.retrorender.engine.PauseMenu;
import wtf.beatrice.retrorender.engine.World3D;
public class GameScreen implements Screen { public class GameScreen implements Screen {
@@ -32,9 +29,11 @@ public class GameScreen implements Screen {
private DebugHud hud; private DebugHud hud;
private boolean showHud = false; private boolean showHud = false;
private boolean paused = false;
private PauseMenu pauseMenu; // menus
private GameSettings settings;
private GameUi gameUi;
private UiMode uiMode = UiMode.GAMEPLAY;
// Shadow Mapping // Shadow Mapping
private ModelBatch shadowBatch; private ModelBatch shadowBatch;
@@ -53,14 +52,10 @@ public class GameScreen implements Screen {
} }
private void initCamera() { private void initCamera() {
camera = new PerspectiveCamera( camera = new PerspectiveCamera(67f, RETRO_WIDTH, RETRO_HEIGHT);
67f, settings = new GameSettings();
RETRO_WIDTH, settings.fov = camera.fieldOfView;
RETRO_HEIGHT cameraController = new FpsCameraController(camera, settings);
);
// near/far + initial direction handled in controller constructor
cameraController = new FpsCameraController(camera);
} }
private void initEnvironment() { private void initEnvironment() {
@@ -75,12 +70,12 @@ public class GameScreen implements Screen {
// shadow-casting directional light // shadow-casting directional light
shadowLight = new DirectionalShadowLight( shadowLight = new DirectionalShadowLight(
1024, 1024, // shadow map resolution 1024, 1024, // shadow map resolution
60f, 60f, // viewport size 60f, 60f, // viewport size
1f, 50f // near/far for the light camera 1f, 50f // near/far for the light camera
); );
shadowLight.set( shadowLight.set(
1.0f, 0.85f, 0.9f, // casting light color 1.0f, 0.85f, 0.9f, // light color
-0.7f, -1.0f, -0.3f // direction -0.7f, -1.0f, -0.3f // direction
); );
environment.add(shadowLight); environment.add(shadowLight);
@@ -95,12 +90,10 @@ public class GameScreen implements Screen {
private void initMenus() { private void initMenus() {
hud = new DebugHud(); hud = new DebugHud();
pauseMenu = new PauseMenu(); gameUi = new GameUi(settings);
} }
// --- screen methods
private void initRetroBuffer() { private void initRetroBuffer() {
// RGB + depth
frameBuffer = new FrameBuffer( frameBuffer = new FrameBuffer(
com.badlogic.gdx.graphics.Pixmap.Format.RGBA8888, com.badlogic.gdx.graphics.Pixmap.Format.RGBA8888,
RETRO_WIDTH, RETRO_WIDTH,
@@ -111,10 +104,9 @@ public class GameScreen implements Screen {
Texture fbTex = frameBuffer.getColorBufferTexture(); Texture fbTex = frameBuffer.getColorBufferTexture();
frameRegion = new TextureRegion(fbTex); frameRegion = new TextureRegion(fbTex);
fbTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest); fbTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest);
// FBO textures are Y-flipped in libGDX, so flip once here
frameRegion.flip(false, true); frameRegion.flip(false, true);
screenBatch = new com.badlogic.gdx.graphics.g2d.SpriteBatch(); screenBatch = new SpriteBatch();
} }
@Override @Override
@@ -134,24 +126,25 @@ public class GameScreen implements Screen {
// ESC: toggle pause and mouse capture // ESC: toggle pause and mouse capture
if (Gdx.input.isKeyJustPressed(Input.Keys.ESCAPE)) { if (Gdx.input.isKeyJustPressed(Input.Keys.ESCAPE)) {
paused = !paused; UiMode newMode = gameUi.onEsc(uiMode);
if (paused) { boolean goingToGameplay = (newMode == UiMode.GAMEPLAY);
cameraController.releaseMouse(); uiMode = newMode;
} else {
if (uiMode == UiMode.GAMEPLAY) {
cameraController.captureMouse(); cameraController.captureMouse();
} else {
cameraController.releaseMouse();
} }
} }
if (!paused) { boolean gameplay = (uiMode == UiMode.GAMEPLAY);
if (gameplay) {
cameraController.update(delta, world); cameraController.update(delta, world);
world.update(delta); world.update(delta);
} }
if (Gdx.input.isKeyJustPressed(Input.Keys.TAB)) { // --- shadow pass ---
showHud = !showHud;
}
// --- shadow pass: render depth from light's point of view
shadowLight.begin(new com.badlogic.gdx.math.Vector3(0f, 0f, 0f), camera.direction); shadowLight.begin(new com.badlogic.gdx.math.Vector3(0f, 0f, 0f), camera.direction);
shadowBatch.begin(shadowLight.getCamera()); shadowBatch.begin(shadowLight.getCamera());
@@ -160,7 +153,7 @@ public class GameScreen implements Screen {
shadowLight.end(); shadowLight.end();
// --- render scene into low res framebuffer // --- render scene into low res framebuffer ---
frameBuffer.begin(); frameBuffer.begin();
Gdx.gl.glViewport(0, 0, RETRO_WIDTH, RETRO_HEIGHT); Gdx.gl.glViewport(0, 0, RETRO_WIDTH, RETRO_HEIGHT);
Gdx.gl.glClearColor(0.5f, 0.6f, 1.0f, 1f); Gdx.gl.glClearColor(0.5f, 0.6f, 1.0f, 1f);
@@ -168,85 +161,89 @@ public class GameScreen implements Screen {
camera.update(); camera.update();
modelBatch.begin(camera); modelBatch.begin(camera);
world.render(modelBatch, environment); // DefaultShader uses shadowLight + env.shadowMap world.render(modelBatch, environment);
modelBatch.end(); modelBatch.end();
// --- HUD // HUD
if (showHud) if (Gdx.input.isKeyJustPressed(Input.Keys.TAB)) {
showHud = !showHud;
}
if (showHud) {
hud.render( hud.render(
RETRO_WIDTH, RETRO_WIDTH,
RETRO_HEIGHT, RETRO_HEIGHT,
camera, camera,
cameraController.getYaw(), cameraController.getYaw(),
cameraController.getPitch()); cameraController.getPitch()
);
}
// pause menu // pause UI
if (paused && pauseMenu != null) { if (uiMode != UiMode.GAMEPLAY) {
pauseMenu.render(RETRO_WIDTH, RETRO_HEIGHT); gameUi.render(RETRO_WIDTH, RETRO_HEIGHT, uiMode);
} }
frameBuffer.end(); frameBuffer.end();
// -- scale framebuffer to screen, preserve aspect ratio -- // -- scale framebuffer to screen, preserve aspect ratio --
int windowW = Gdx.graphics.getWidth(); // logical size int windowW = Gdx.graphics.getWidth();
int windowH = Gdx.graphics.getHeight(); int windowH = Gdx.graphics.getHeight();
int backW = Gdx.graphics.getBackBufferWidth();
int backH = Gdx.graphics.getBackBufferHeight();
int backW = Gdx.graphics.getBackBufferWidth(); // actual GL framebuffer RetroViewportHelper.RetroViewport vp =
int backH = Gdx.graphics.getBackBufferHeight(); RetroViewportHelper.computeViewport(windowW, windowH, RETRO_WIDTH, RETRO_HEIGHT);
// compute how much we can scale the FBO into the window // click → UI
float scale = Math.min( if (uiMode != UiMode.GAMEPLAY && Gdx.input.justTouched()) {
windowW / (float) RETRO_WIDTH,
windowH / (float) RETRO_HEIGHT
);
int drawW = Math.round(RETRO_WIDTH * scale);
int drawH = Math.round(RETRO_HEIGHT * scale);
// center the image
int offsetX = (windowW - drawW) / 2;
int offsetY = (windowH - drawH) / 2;
// --- handle mouse click on pause button (window -> retro coords) ---
if (paused && pauseMenu != null && Gdx.input.justTouched()) {
int mouseX = Gdx.input.getX(); int mouseX = Gdx.input.getX();
int mouseY = Gdx.graphics.getHeight() - Gdx.input.getY(); // to bottom-left origin int mouseY = Gdx.input.getY();
// is the click inside the scaled game area? RetroViewportHelper.RetroClick rc =
if (mouseX >= offsetX && mouseX <= offsetX + drawW && RetroViewportHelper.toRetroCoords(
mouseY >= offsetY && mouseY <= offsetY + drawH) { vp,
RETRO_WIDTH, RETRO_HEIGHT,
mouseX, mouseY,
windowH
);
float relX = (mouseX - offsetX) / (float) drawW; if (rc.inside) {
float relY = (mouseY - offsetY) / (float) drawH; GameUi.UiResult result = gameUi.handleClick(rc.x, rc.y, uiMode);
float retroX = relX * RETRO_WIDTH; switch (result) {
float retroY = relY * RETRO_HEIGHT;
PauseMenu.MenuAction action = pauseMenu.getActionAt(retroX, retroY);
switch (action) {
case RESUME: case RESUME:
paused = false; uiMode = UiMode.GAMEPLAY;
cameraController.onShow(); cameraController.captureMouse();
break; break;
case OPEN_SETTINGS:
uiMode = UiMode.SETTINGS;
// mouse already released when we entered PAUSE, so nothing to do
break;
case CLOSE_SETTINGS:
uiMode = UiMode.PAUSE;
// stay in pause, mouse remains free
break;
case QUIT: case QUIT:
Gdx.app.exit(); Gdx.app.exit();
break; break;
case NONE:
default: default:
break; break;
} }
} }
} }
// viewport in backbuffer coordinates // viewport in backbuffer coordinates
// final draw using vp.offsetX / vp.offsetY / vp.drawW / vp.drawH
Gdx.gl.glViewport(0, 0, backW, backH); Gdx.gl.glViewport(0, 0, backW, backH);
Gdx.gl.glClearColor(0f, 0f, 0f, 1f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// projection in window (logical) coordinates
screenBatch.getProjectionMatrix().setToOrtho2D(0, 0, windowW, windowH); screenBatch.getProjectionMatrix().setToOrtho2D(0, 0, windowW, windowH);
screenBatch.begin(); screenBatch.begin();
screenBatch.draw(frameRegion, offsetX, offsetY, drawW, drawH); screenBatch.draw(frameRegion, vp.offsetX, vp.offsetY, vp.drawW, vp.drawH);
screenBatch.end(); screenBatch.end();
} }
@@ -273,10 +270,9 @@ public class GameScreen implements Screen {
world.dispose(); world.dispose();
modelBatch.dispose(); modelBatch.dispose();
hud.dispose(); hud.dispose();
shadowBatch.dispose(); shadowBatch.dispose();
shadowLight.dispose(); shadowLight.dispose();
if (pauseMenu != null) pauseMenu.dispose(); if (gameUi != null) gameUi.dispose();
if (frameBuffer != null) frameBuffer.dispose(); if (frameBuffer != null) frameBuffer.dispose();
if (screenBatch != null) screenBatch.dispose(); if (screenBatch != null) screenBatch.dispose();
} }

View File

@@ -10,10 +10,9 @@ import com.badlogic.gdx.math.Vector3;
public class FpsCameraController { public class FpsCameraController {
private final PerspectiveCamera camera; private final PerspectiveCamera camera;
private final GameSettings settings;
// movement + look // movement + look
private float moveSpeed = 5f; // units / second
private float mouseSensitivity = 0.15f;
private float yaw = 0f; private float yaw = 0f;
private float pitch = 0f; private float pitch = 0f;
@@ -45,10 +44,10 @@ public class FpsCameraController {
private Cursor invisibleCursor; private Cursor invisibleCursor;
public FpsCameraController(PerspectiveCamera camera) { public FpsCameraController(PerspectiveCamera camera, GameSettings settings) {
this.camera = camera; this.camera = camera;
this.settings = settings;
// default camera setup
camera.position.set(0f, eyeHeight, 6f); camera.position.set(0f, eyeHeight, 6f);
camera.near = 0.1f; camera.near = 0.1f;
camera.far = 100f; camera.far = 100f;
@@ -128,8 +127,8 @@ public class FpsCameraController {
// recenter for next frame // recenter for next frame
Gdx.input.setCursorPosition(centerX, centerY); Gdx.input.setCursorPosition(centerX, centerY);
float deltaX = -dx * mouseSensitivity; float deltaX = -dx * settings.mouseSensitivity;
float deltaY = -dy * mouseSensitivity; float deltaY = -dy * settings.mouseSensitivity;
yaw += deltaX; yaw += deltaX;
pitch += deltaY; pitch += deltaY;
@@ -149,7 +148,9 @@ public class FpsCameraController {
// right vector (perpendicular) // right vector (perpendicular)
tmpRight.set(tmpForward.z, 0f, -tmpForward.x).nor(); tmpRight.set(tmpForward.z, 0f, -tmpForward.x).nor();
float moveAmount = moveSpeed * delta; camera.fieldOfView = settings.fov;
float moveAmount = settings.moveSpeed * delta;
float moveX = 0f; float moveX = 0f;
float moveZ = 0f; float moveZ = 0f;
@@ -260,7 +261,4 @@ public class FpsCameraController {
public float getYaw() { return yaw; } public float getYaw() { return yaw; }
public float getPitch() { return pitch; } public float getPitch() { return pitch; }
public void setMoveSpeed(float moveSpeed) { this.moveSpeed = moveSpeed; }
public void setMouseSensitivity(float mouseSensitivity) { this.mouseSensitivity = mouseSensitivity; }
} }

View File

@@ -0,0 +1,11 @@
package wtf.beatrice.retrorender.engine;
public class GameSettings {
public float fov = 67f; // degrees
public float mouseSensitivity = 0.15f;
public float moveSpeed = 5f;
public float minFov = 40f;
public float maxFov = 100f;
}

View File

@@ -0,0 +1,87 @@
package wtf.beatrice.retrorender.engine;
public class GameUi {
public enum UiResult {
NONE,
RESUME,
OPEN_SETTINGS,
CLOSE_SETTINGS,
QUIT
}
private final PauseMenu pauseMenu;
private final SettingsMenu settingsMenu;
private final GameSettings settings;
public GameUi(GameSettings settings) {
this.settings = settings;
this.pauseMenu = new PauseMenu();
this.settingsMenu = new SettingsMenu();
// sync initial settings
this.settingsMenu.setFov(settings.fov);
}
public UiMode onEsc(UiMode current) {
switch (current) {
case GAMEPLAY:
return UiMode.PAUSE;
case PAUSE:
case SETTINGS:
return UiMode.GAMEPLAY;
default:
return UiMode.GAMEPLAY;
}
}
public void render(int retroW, int retroH, UiMode mode) {
switch (mode) {
case PAUSE:
pauseMenu.render(retroW, retroH);
break;
case SETTINGS:
settingsMenu.render(retroW, retroH);
break;
case GAMEPLAY:
default:
break;
}
}
public UiResult handleClick(float retroX, float retroY, UiMode mode) {
if (mode == UiMode.SETTINGS) {
boolean close = settingsMenu.handleClick(retroX, retroY);
// always sync FOV to settings
settings.fov = settingsMenu.getFov();
if (close) {
// Settings “Close” button clicked -> go back to PAUSE
return UiResult.CLOSE_SETTINGS;
}
return UiResult.NONE;
}
if (mode == UiMode.PAUSE) {
PauseMenu.MenuAction action = pauseMenu.getActionAt(retroX, retroY);
return switch (action)
{
case RESUME -> UiResult.RESUME;
case QUIT -> UiResult.QUIT;
case SETTINGS -> UiResult.OPEN_SETTINGS;
default -> UiResult.NONE;
};
}
return UiResult.NONE;
}
public void dispose() {
pauseMenu.dispose();
settingsMenu.dispose();
}
public SettingsMenu getSettingsMenu() {
return settingsMenu;
}
}

View File

@@ -22,6 +22,7 @@ public class PauseMenu {
public enum MenuAction { public enum MenuAction {
NONE, NONE,
RESUME, RESUME,
SETTINGS,
QUIT QUIT
} }
@@ -62,9 +63,10 @@ public class PauseMenu {
font = generator.generateFont(param); font = generator.generateFont(param);
generator.dispose(); generator.dispose();
// create buttons // create buttons (ordered left → right)
buttons.add(new Button(" Resume ", MenuAction.RESUME)); buttons.add(new Button(" Resume ", MenuAction.RESUME));
buttons.add(new Button(" Quit ", MenuAction.QUIT)); buttons.add(new Button(" Settings ", MenuAction.SETTINGS));
buttons.add(new Button(" Quit ", MenuAction.QUIT));
// 1x1 pixel texture (white, we tint it) // 1x1 pixel texture (white, we tint it)
Pixmap pm = new Pixmap(1, 1, Pixmap.Format.RGBA8888); Pixmap pm = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
@@ -79,14 +81,14 @@ public class PauseMenu {
batch.begin(); batch.begin();
// --- black bar at bottom --- // --- black bar at bottom ---
float barH = 40f; // in retro pixels float barH = 80f; // in retro pixels
float barY = 0f; float barY = 0f;
batch.setColor(0f, 0f, 0f, 0.9f); batch.setColor(0f, 0f, 0f, 0.9f);
batch.draw(pixel, 0f, barY, width, barH); batch.draw(pixel, 0f, barY, width, barH);
// --- buttons --- // --- buttons ---
float spacing = 8f; // horizontal space between buttons float spacing = 10f; // horizontal space between buttons
// measure each button label // measure each button label
GlyphLayout layout = new GlyphLayout(); GlyphLayout layout = new GlyphLayout();
@@ -96,7 +98,7 @@ public class PauseMenu {
for (Button b : buttons) { for (Button b : buttons) {
layout.setText(font, b.label); layout.setText(font, b.label);
float bw = layout.width + 16f; // padding float bw = layout.width + 16f; // padding
float bh = layout.height + 8f; float bh = layout.height + 12f;
b.w = bw; b.w = bw;
b.h = bh; b.h = bh;
totalWidth += bw; totalWidth += bw;

View File

@@ -0,0 +1,60 @@
package wtf.beatrice.retrorender.engine;
public class RetroViewportHelper {
public static class RetroViewport {
public int offsetX, offsetY;
public int drawW, drawH;
public float scale;
}
public static class RetroClick {
public boolean inside;
public float x, y; // retro coords
}
public static RetroViewport computeViewport(
int windowW, int windowH,
int retroW, int retroH) {
RetroViewport vp = new RetroViewport();
float scale = Math.min(
windowW / (float) retroW,
windowH / (float) retroH
);
vp.scale = scale;
vp.drawW = Math.round(retroW * scale);
vp.drawH = Math.round(retroH * scale);
vp.offsetX = (windowW - vp.drawW) / 2;
vp.offsetY = (windowH - vp.drawH) / 2;
return vp;
}
public static RetroClick toRetroCoords(
RetroViewport vp,
int retroW, int retroH,
int mouseX, int mouseYWindow,
int windowHeight) {
RetroClick rc = new RetroClick();
// libGDX gives mouse from top-left; you already invert Y like this:
int yBottomOrigin = windowHeight - mouseYWindow;
if (mouseX < vp.offsetX || mouseX > vp.offsetX + vp.drawW ||
yBottomOrigin < vp.offsetY || yBottomOrigin > vp.offsetY + vp.drawH) {
rc.inside = false;
return rc;
}
float relX = (mouseX - vp.offsetX) / (float) vp.drawW;
float relY = (yBottomOrigin - vp.offsetY) / (float) vp.drawH;
rc.inside = true;
rc.x = relX * retroW;
rc.y = relY * retroH;
return rc;
}
}

View File

@@ -0,0 +1,219 @@
package wtf.beatrice.retrorender.engine;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
import com.badlogic.gdx.math.MathUtils;
public class SettingsMenu {
private final SpriteBatch batch;
private final BitmapFont font;
private final Texture pixel;
// FOV value stored here, GameScreen syncs it to the camera
private float fov;
private final float minFov = 40f;
private final float maxFov = 100f;
private final float fovStep = 2f;
// button / hit areas
private float fovDecX, fovDecY, fovDecW, fovDecH;
private float fovIncX, fovIncY, fovIncW, fovIncH;
private float closeX, closeY, closeW, closeH;
public SettingsMenu() {
batch = new SpriteBatch();
FreeTypeFontGenerator generator =
new FreeTypeFontGenerator(Gdx.files.internal("fonts/red-hat-mono.ttf"));
FreeTypeFontGenerator.FreeTypeFontParameter param =
new FreeTypeFontGenerator.FreeTypeFontParameter();
param.size = 12;
param.color = Color.WHITE;
param.mono = true;
param.hinting = FreeTypeFontGenerator.Hinting.None;
param.borderWidth = 0.2f;
param.borderColor = Color.WHITE;
param.shadowOffsetX = 0;
param.shadowOffsetY = 0;
param.minFilter = Texture.TextureFilter.Nearest;
param.magFilter = Texture.TextureFilter.Nearest;
font = generator.generateFont(param);
generator.dispose();
// 1x1 pixel
Pixmap pm = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
pm.setColor(1f, 1f, 1f, 1f);
pm.fill();
pixel = new Texture(pm);
pm.dispose();
// default FOV
fov = 67f;
}
public void setFov(float fov) {
this.fov = MathUtils.clamp(fov, minFov, maxFov);
}
public float getFov() {
return fov;
}
/**
* @param width RETRO_WIDTH
* @param height RETRO_HEIGHT
*/
public void render(int width, int height) {
batch.getProjectionMatrix().setToOrtho2D(0, 0, width, height);
batch.begin();
GlyphLayout layout = new GlyphLayout();
// --- central panel ---
float panelW = 200f;
float panelH = 220f;
float panelX = (width - panelW) / 2f;
float panelY = (height - panelH) / 2f;
// background
batch.setColor(0f, 0f, 0f, 0.9f);
batch.draw(pixel, panelX, panelY, panelW, panelH);
// border
batch.setColor(1f, 1f, 1f, 1f);
float border = 1f;
// --- title ---
String title = "Settings";
layout.setText(font, title);
float titleX = panelX + (panelW - layout.width) / 2f;
float titleY = panelY + panelH - 10f;
font.draw(batch, layout, titleX, titleY);
// small separator line under title
batch.draw(pixel, panelX + 8f, titleY - 14f, panelW - 16f, 1f);
// --- FOV row ---
float rowY = panelY + panelH - 50f;
float rowH = 18f;
// label
String fovLabel = "FOV";
layout.setText(font, fovLabel);
float labelX = panelX + 12f;
float labelY = rowY + rowH - 4f;
font.draw(batch, layout, labelX, labelY);
// buttons + value area
fovDecW = 20f;
fovDecH = rowH;
fovDecX = panelX + 80f;
fovDecY = rowY;
fovIncW = 20f;
fovIncH = rowH;
fovIncX = panelX + panelW - 32f;
fovIncY = rowY;
// draw dec button "<"
drawButtonWithLabel("<", fovDecX, fovDecY, fovDecW, fovDecH);
// draw inc button ">"
drawButtonWithLabel(">", fovIncX, fovIncY, fovIncW, fovIncH);
// numeric value centered between dec/inc
String fovText = String.format("%3.0f°", fov);
layout.setText(font, fovText);
float valueCenter = (fovDecX + fovDecW + fovIncX) / 2f;
float valueX = valueCenter - layout.width / 2f;
float valueY = rowY + rowH - 4f;
font.draw(batch, layout, valueX, valueY);
// --- Close button at bottom ---
String closeLabel = " Close ";
layout.setText(font, closeLabel);
closeW = layout.width + 16f;
closeH = layout.height + 8f;
closeX = panelX + (panelW - closeW) / 2f;
closeY = panelY + 16f;
// button bg + border
batch.setColor(0.1f, 0.1f, 0.1f, 1f);
batch.draw(pixel, closeX, closeY, closeW, closeH);
batch.setColor(1f, 1f, 1f, 1f);
// border
batch.draw(pixel, closeX, closeY + closeH - border, closeW, border);
batch.draw(pixel, closeX, closeY, closeW, border);
batch.draw(pixel, closeX, closeY, border, closeH);
batch.draw(pixel, closeX + closeW - border, closeY, border, closeH);
float closeTextX = closeX + (closeW - layout.width) / 2f;
float closeTextY = closeY + closeH - (closeH - layout.height) / 2f - 2f;
font.draw(batch, closeLabel, closeTextX, closeTextY);
batch.end();
}
private void drawButtonWithLabel(String text, float x, float y, float w, float h) {
// background
batch.setColor(0.1f, 0.1f, 0.1f, 1f);
batch.draw(pixel, x, y, w, h);
// border
batch.setColor(1f, 1f, 1f, 1f);
float border = 1f;
batch.draw(pixel, x, y + h - border, w, border);
batch.draw(pixel, x, y, w, border);
batch.draw(pixel, x, y, border, h);
batch.draw(pixel, x + w - border, y, border, h);
GlyphLayout layout = new GlyphLayout(font, text);
float tx = x + (w - layout.width) / 2f;
float ty = y + h - (h - layout.height) / 2f - 2f;
font.draw(batch, layout, tx, ty);
}
/**
* Handle a click in RETRO coords.
*
* @return true if the menu wants to close (Close button clicked)
*/
public boolean handleClick(float x, float y) {
// FOV -
if (x >= fovDecX && x <= fovDecX + fovDecW &&
y >= fovDecY && y <= fovDecY + fovDecH) {
fov = MathUtils.clamp(fov - fovStep, minFov, maxFov);
return false;
}
// FOV +
if (x >= fovIncX && x <= fovIncX + fovIncW &&
y >= fovIncY && y <= fovIncY + fovIncH) {
fov = MathUtils.clamp(fov + fovStep, minFov, maxFov);
return false;
}
// Close
if (x >= closeX && x <= closeX + closeW &&
y >= closeY && y <= closeY + closeH) {
return true;
}
return false;
}
public void dispose() {
batch.dispose();
font.dispose();
pixel.dispose();
}
}

View File

@@ -0,0 +1,7 @@
package wtf.beatrice.retrorender.engine;
public enum UiMode {
GAMEPLAY,
PAUSE, // only bottom bar
SETTINGS // pause bar + center settings
}