feat(ui): Simple escape menu

This commit is contained in:
Jakub Žitník 2025-04-02 20:27:46 +02:00
parent aced312df1
commit 4dd785fdfc
Signed by: jzitnik
GPG Key ID: C577A802A6AF4EF3
8 changed files with 186 additions and 26 deletions

View File

@ -1,5 +1,6 @@
package cz.jzitnik.game.entities;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.GameSaver;
import cz.jzitnik.game.core.sound.Sound;
import cz.jzitnik.game.handlers.events.EventHandlerProvider;
@ -23,5 +24,9 @@ public class Dependencies {
public ToolUseProvider toolUseProvider = new ToolUseProvider();
public Sound sound = new Sound();
public Font font = new Font();
public Escape escape = new Escape();
public Escape escape;
public Dependencies(Game game) {
escape = new Escape(game);
}
}

View File

@ -11,6 +11,6 @@ public class GameStates {
public GameStates(Game game) {
craftingTable = new CraftingTable(game);
dependencies = new Dependencies();
dependencies = new Dependencies(game);
}
}

View File

@ -6,11 +6,11 @@ import cz.jzitnik.game.annotations.ItemRegistry;
import cz.jzitnik.game.entities.items.Item;
import cz.jzitnik.game.entities.items.ItemType;
import cz.jzitnik.game.sprites.Dye;
import cz.jzitnik.game.sprites.WoolItem;
@Fuel(0.5)
@ItemRegistry("orange_dye")
public class OrangeDyeItem extends Item {
// VGhlIHBlcnNvbiBJIHRydXN0ZWQgdGhlIG1vc3QganVzdCBsaWVkIHRvIG1lLi4u
public OrangeDyeItem() {
super("Orange_dye", "Orange dye", ItemType.USELESS_ITEM, SpriteLoader.SPRITES.ITEM_DYE);
setSpriteState(Dye.DyeState.ORANGE);

View File

@ -1,9 +1,11 @@
package cz.jzitnik.game.sprites.ui;
import java.util.Arrays;
import java.util.function.Function;
import org.jline.terminal.Terminal;
import cz.jzitnik.game.sprites.ui.Font.Custom.Width;
import cz.jzitnik.tui.ResourceLoader;
import lombok.AllArgsConstructor;
import lombok.Getter;
@ -24,7 +26,7 @@ public class Font {
@Getter
@AllArgsConstructor
private class CharDTO {
public class CharDTO {
private String character;
private int width;
}
@ -35,9 +37,9 @@ public class Font {
for (int y = startY; y < startY + height; y++) {
String[] line = lines[y].split("\033");
for (int x = startX; x < startX + width - 1; x++) {
for (int x = startX; x < startX + width; x++) {
stringBuilder.append("\033")
.append(line[x].replaceAll("\\[(?!49m)[0-9;]+m", color).replaceAll("\\[49m", background));
.append(line[x + 1].replaceAll("\\[(?!49m)[0-9;]+m", color).replaceAll("\\[49m", background));
}
stringBuilder.append("\033[0m\n");
}
@ -61,7 +63,7 @@ public class Font {
public CharDTO getChar(char character, String color, String background) {
if (character == ' ') {
return new CharDTO(("\033[49m ".repeat(12) + "\n").repeat(HEIGHT), 12);
return new CharDTO((("\033" + background + " ").repeat(12) + "\n").repeat(HEIGHT), 12);
}
if (Character.isDigit(character)) {
@ -175,11 +177,18 @@ public class Font {
public record Background(String bg) {
}
public record Color(String color) {
}
private FontDTO applyAlignment(FontDTO data, Align alignment, int termWidth) {
public class Custom {
public record Width(int width) {
}
}
private FontDTO applyAlignment(FontDTO data, Align alignment, int termWidth, String background) {
return switch (alignment) {
case LEFT -> data;
case CENTER -> {
@ -189,10 +198,15 @@ public class Font {
StringBuilder buffer = new StringBuilder();
var leftPad = (termWidth - nowWidth) / 2;
var rightPad = termWidth - nowWidth - leftPad;
for (int i = 0; i < lines.length; i++) {
buffer.append("\033").append(background);
buffer.append(" ".repeat(leftPad)).append(lines[i]);
buffer.append("\n");
log.debug("Ansi escape codes: {}", Arrays.asList(lines[i].split("\033")));
buffer.append("\033").append(background);
buffer.append(" ".repeat(rightPad));
buffer.append("\033[0m\n");
}
yield new FontDTO(buffer.toString(), termWidth, data.getHeight());
@ -203,9 +217,10 @@ public class Font {
StringBuilder buffer = new StringBuilder();
var leftPad = termWidth - nowWidth;
var leftPad = (int) Math.round((termWidth - nowWidth) / 2.0);
for (int i = 0; i < lines.length; i++) {
buffer.append("\033").append(background);
buffer.append(" ".repeat(leftPad)).append(lines[i]);
buffer.append("\n");
}
@ -224,6 +239,7 @@ public class Font {
String color = "[47m";
String background = "[49m";
int width = termWidth;
for (Object attribute : attributes) {
if (Size.class.isAssignableFrom(attribute.getClass())) {
@ -246,9 +262,15 @@ public class Font {
continue;
}
if (Custom.Width.class.isAssignableFrom(attribute.getClass())) {
width = ((Width) attribute).width();
continue;
}
log.error("Invalid attribute class {}. Skipping", attribute.getClass().getSimpleName());
}
return applyAlignment(getScaledLine(text, fontSize, termWidth, color, background), alignment, termWidth);
return applyAlignment(getScaledLine(text, fontSize, termWidth, color, background), alignment, width,
background);
}
}

View File

@ -55,6 +55,7 @@ public class InputHandlerThread extends Thread {
.getGameStates().clickX].stream().filter(i -> i.getBlockId().equals("furnace"))
.toList().getFirst().getData()).click(game, mouseEvent, terminal,
screenRenderer);
case ESC -> game.getGameStates().dependencies.escape.mouse(mouseEvent, terminal, screenRenderer);
}
}
}
@ -89,6 +90,7 @@ public class InputHandlerThread extends Thread {
game.setWindow(Window.WORLD);
} else {
game.setWindow(Window.ESC);
game.getGameStates().dependencies.escape.reset();
}
screenRenderer.render(game);
// System.out.println("Exiting game...");

View File

@ -1,13 +1,29 @@
package cz.jzitnik.game.ui;
import org.jline.terminal.MouseEvent;
import org.jline.terminal.Terminal;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.sprites.ui.Font;
import cz.jzitnik.game.sprites.ui.Font.*;
import cz.jzitnik.tui.ScreenRenderer;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Escape {
private Game game;
private int btnWidth;
private int leftPad;
private int mainTextHeight;
private int buttonHeight;
private int textButtonMargin;
private boolean[] buttonsHover = { false, false, false };
public Escape(Game game) {
this.game = game;
}
public void render(StringBuilder buffer, Terminal terminal, Game game) {
var font = game.getGameStates().dependencies.font;
var width = terminal.getWidth();
@ -18,5 +34,121 @@ public class Escape {
var twodcraft = font.line(terminal, "2DCraft", Size.LARGE, Align.CENTER);
buffer.append(twodcraft.getData());
mainTextHeight = twodcraft.getHeight();
if (height < 600) {
textButtonMargin = 15;
} else if (height < 800) {
textButtonMargin = 30;
} else {
textButtonMargin = 50;
}
buffer.append("\n".repeat(textButtonMargin));
renderButton(buffer, "Continue", width, font, terminal, buttonsHover[0]);
buffer.append("\n".repeat(textButtonMargin / 2));
renderButton(buffer, "Options", width, font, terminal, buttonsHover[1]);
buffer.append("\n".repeat(textButtonMargin / 2));
renderButton(buffer, "Save and exit", width, font, terminal, buttonsHover[2]);
}
public void mouse(MouseEvent mouseEvent, Terminal terminal, ScreenRenderer screenRenderer) {
int x = mouseEvent.getX();
int y = mouseEvent.getY();
var type = mouseEvent.getType();
int buttonx = x - leftPad;
int buttony = y - (mainTextHeight + textButtonMargin);
int margin = textButtonMargin / 2;
boolean changed = false;
if (buttonx > 0 && buttonx <= btnWidth) {
if (buttony > 0 && buttony < buttonHeight) {
if (type == MouseEvent.Type.Pressed) {
game.setWindow(Window.WORLD);
screenRenderer.render(game);
return;
}
if (buttonsHover[0] == false) {
buttonsHover[0] = true;
changed = true;
}
} else if (buttonsHover[0]) {
buttonsHover[0] = false;
changed = true;
}
if (buttony > buttonHeight + margin && buttony < 2 * buttonHeight + margin) {
if (type == MouseEvent.Type.Pressed) {
return;
}
if (buttonsHover[1] == false) {
buttonsHover[1] = true;
changed = true;
}
} else if (buttonsHover[1]) {
buttonsHover[1] = false;
changed = true;
}
if (buttony > 2 * (buttonHeight + margin) && buttony < buttonHeight + 2 * (buttonHeight + margin)) {
if (type == MouseEvent.Type.Pressed) {
return;
}
if (buttonsHover[2] == false) {
buttonsHover[2] = true;
changed = true;
}
} else if (buttonsHover[2]) {
buttonsHover[2] = false;
changed = true;
}
}
if (changed) {
screenRenderer.render(game);
}
}
private void renderButton(StringBuilder buffer, String txt, int width, Font font, Terminal terminal,
boolean selected) {
int btnWidth = Math.min(350, (int) (width * (3.0 / 4)));
this.btnWidth = btnWidth;
int leftPad = (width - btnWidth) / 2;
this.leftPad = leftPad;
log.debug("Button width: {}px ", btnWidth);
final String color = selected ? "[48;2;70;70;70m" : "[48;2;116;115;113m";
var text = font.line(terminal, txt, Size.SMALL, Align.CENTER, new Custom.Width(btnWidth - 4),
new Background(color));
int btnHeight = text.getHeight() + 4;
this.buttonHeight = btnHeight;
var lines = text.getData().split("\n");
for (int y = 0; y < btnHeight; y++) {
buffer.append(" ".repeat(leftPad));
if (y == 0 || y == 1 || y == btnHeight - 1 || y == btnHeight - 2) {
buffer.append("\033").append(color).append(" ".repeat(btnWidth));
} else {
buffer.append("\033").append(color).append(" ".repeat(2));
buffer.append(lines[y - 2]);
buffer.append("\033").append(color).append(" ".repeat(2));
}
buffer.append("\033[0m\n");
}
}
public void reset() {
for (int i = 0; i < buttonsHover.length; i++) {
buttonsHover[i] = false;
}
}
}

View File

@ -7,7 +7,6 @@ import cz.jzitnik.game.sprites.Air;
import cz.jzitnik.game.sprites.SimpleSprite;
import cz.jzitnik.game.blocks.Chest;
import cz.jzitnik.game.blocks.Furnace;
import cz.jzitnik.game.ui.Escape;
import cz.jzitnik.game.ui.Healthbar;
import cz.jzitnik.tui.utils.SpriteCombiner;
import lombok.Getter;

View File

@ -1,14 +1,14 @@