Working on menus

This commit is contained in:
2026-05-14 13:01:53 +02:00
parent 367f37e6f8
commit 412e3adf2a
9 changed files with 215 additions and 9 deletions
+4
View File
@@ -12,6 +12,10 @@ val slf4j = "2.0.18"
val lombokVersion = "1.18.46"
val avajeInjectVersion = "12.5"
application {
mainClass = "be.seeseemelk.diceos.Bootloader"
}
repositories {
mavenCentral()
}
@@ -10,6 +10,7 @@ public class DiceOS {
private static DiceOS INSTANCE;
private final ResourceLoader resourceLoader;
private final WindowService windowService;
private final InputService inputService;
@PostConstruct
void register() {
@@ -33,4 +34,8 @@ public class DiceOS {
public static WindowService getWindowService() {
return INSTANCE.windowService;
}
public static InputService getInputService() {
return INSTANCE.inputService;
}
}
@@ -5,7 +5,6 @@ import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.ScreenUtils;
import com.badlogic.gdx.utils.viewport.ScreenViewport;
@@ -24,6 +23,7 @@ public class DiceOSAdapter extends ApplicationAdapter {
private final DisplayService display;
private final CursorService cursorService;
private final WindowService windowService;
private final InputService inputService;
private final List<OnStartup> startupTasks;
private Texture clouds;
private Texture border;
@@ -62,6 +62,8 @@ public class DiceOSAdapter extends ApplicationAdapter {
offsetY = (height - (displayHeight * scaling)) / 2;
cursorService.setScale(scaling);
inputService.setScale(scaling);
inputService.setOffset(offsetX, offsetY);
gc = new GraphicsContext(screenViewport.getCamera(), display.getBatch(), displayHeight, displayWidth,
DiceOS.getResourceLoader().getDefaultFont().getBitmapFont());
@@ -8,13 +8,14 @@ import io.avaje.inject.Component;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.awt.event.KeyEvent;
@Slf4j
@Component
@RequiredArgsConstructor
public class InputService implements OnStartup {
private final DisplayService displayService;
private int scale = 1;
private int offsetX = 0;
private int offsetY = 0;
@Override
public void onStartup() {
@@ -49,4 +50,36 @@ public class InputService implements OnStartup {
return Gdx.input.isKeyPressed(com.badlogic.gdx.Input.Keys.ALT_LEFT) ||
Gdx.input.isKeyPressed(com.badlogic.gdx.Input.Keys.ALT_RIGHT);
}
public int getMouseX() {
return (Gdx.input.getX() - offsetX) / scale;
}
public int getMouseY() {
return (Gdx.input.getY() - offsetY) / scale;
}
void setScale(int scale) {
this.scale = scale;
}
void setOffset(int x, int y) {
this.offsetX = x;
this.offsetY = y;
}
/**
* Checks if the mouse is currently within the specified rectangle.
* @param x The x-coordinate of the rectangle.
* @param y The y-coordinate of the rectangle.
* @param width The width of the rectangle.
* @param height The height of the rectangle.
* @return True if the mouse is within the rectangle, false otherwise.
*/
public boolean mouseInBounds(int x, int y, int width, int height) {
int mx = getMouseX();
int my = getMouseY();
return mx >= x && mx < x + width &&
my >= y && my < y + height;
}
}
@@ -6,11 +6,14 @@ import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.NinePatch;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Stack;
/**
* Graphics context and rendering utilities.
*/
@@ -23,13 +26,13 @@ public class GraphicsContext {
private final int width;
private final BitmapFont font;
private final java.util.Stack<GraphicsState> stateStack = new java.util.Stack<>();
private final Stack<GraphicsState> stateStack = new java.util.Stack<>();
private static class GraphicsState {
private final com.badlogic.gdx.math.Matrix4 transform;
private final boolean scissorEnabled;
private final Matrix4 transform;
private boolean scissorEnabled;
public GraphicsState(com.badlogic.gdx.math.Matrix4 transform, boolean scissorEnabled) {
public GraphicsState(Matrix4 transform, boolean scissorEnabled) {
this.transform = transform.cpy();
this.scissorEnabled = scissorEnabled;
}
@@ -114,6 +117,19 @@ public class GraphicsContext {
batch.setTransformMatrix(batch.getTransformMatrix().trn(x, -y, 0));
}
/**
* Gets the total amount of X-translation applied by this graphics context.
* In other words, a draw command with `x=0` would actually write to this screen coordinate.
* @return The total amount of X-translation.
*/
public int getXTranslation() {
return (int) batch.getTransformMatrix().val[com.badlogic.gdx.math.Matrix4.M03];
}
public int getYTranslation() {
return (int) batch.getTransformMatrix().val[com.badlogic.gdx.math.Matrix4.M13];
}
/**
* Sets a clipping rectangle for subsequent draw operations.
*
@@ -125,6 +141,15 @@ public class GraphicsContext {
ScissorStack.calculateScissors(camera, batch.getTransformMatrix(), scissors, scissors);
batch.flush();
ScissorStack.pushScissors(scissors);
getCurrentState().scissorEnabled = true;
}
/**
* Returns the current graphics state from the top of the stack.
* @return The current state.
*/
private GraphicsState getCurrentState() {
return stateStack.peek();
}
/**
@@ -146,6 +171,9 @@ public class GraphicsContext {
batch.flush();
GraphicsState state = stateStack.pop();
batch.setTransformMatrix(state.transform);
if (state.scissorEnabled) {
ScissorStack.popScissors();
}
}
/**
@@ -5,12 +5,14 @@ import be.seeseemelk.diceos.system.font.Font;
import be.seeseemelk.diceos.system.gfx.GraphicsContext;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
@RequiredArgsConstructor
@Getter
@Slf4j
public class Menu implements MenuItem {
private final String title;
private final List<MenuItem> items = new ArrayList<>();
@@ -33,7 +35,7 @@ public class Menu implements MenuItem {
@Override
public int getHeight() {
return items.stream().mapToInt(MenuItem::getHeight).sum();
return 13;
}
/**
@@ -49,7 +51,15 @@ public class Menu implements MenuItem {
@Override
public void paint(GraphicsContext gc) {
if (DiceOS.getInputService().mouseInBounds(gc.getXTranslation(), gc.getYTranslation(), getWidth(), getHeight())) {
log.info("Mouse in bounds");
gc.setColour(0, 0, 255);
}
gc.setColour(255, 255, 255);
gc.fillRect(0, 1, getWidth() + 3, getHeight() - 1);
// Renders the menu as a simple menu button
gc.draw(title, 0, 5);
gc.draw(title, 2, 5);
}
}
@@ -12,4 +12,18 @@ public class Utils {
public static boolean isPowerOfTwo(int value) {
return (value & (value - 1)) == 0 && value != 0;
}
/**
* Determines if the point at (x, y) is inside the bounds of (x1, y1, width, height).
* @param x The x-coordinate of the point.
* @param y The y-coordinate of the point.
* @param x1 The x-coordinate of the bounds.
* @param y1 The y-coordinate of the bounds.
* @param width The width of the bounds.
* @param height The height of the bounds.
* @return True if the point is inside the bounds, false otherwise.
*/
public static boolean isInBounds(int x, int y, int x1, int y1, int width, int height) {
return x >= x1 && x < x1 + width && y >= y1 && y < y1 + height;
}
}
@@ -0,0 +1,39 @@
package be.seeseemelk.diceos.system;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.junit.jupiter.api.Assertions.*;
class InputServiceTest {
private DisplayService displayService;
private InputService inputService;
@BeforeEach
void setUp() {
displayService = Mockito.mock(DisplayService.class);
inputService = new InputService(displayService);
Gdx.input = Mockito.mock(Input.class);
}
@Test
void testMouseMethods() {
Mockito.when(Gdx.input.getX()).thenReturn(100);
Mockito.when(Gdx.input.getY()).thenReturn(200);
assertEquals(100, inputService.getMouseX());
assertEquals(200, inputService.getMouseY());
}
@Test
void testMouseInBounds() {
Mockito.when(Gdx.input.getX()).thenReturn(100);
Mockito.when(Gdx.input.getY()).thenReturn(200);
assertTrue(inputService.mouseInBounds(90, 190, 20, 20));
assertFalse(inputService.mouseInBounds(0, 0, 50, 50));
}
}
@@ -0,0 +1,71 @@
package be.seeseemelk.diceos.system.gfx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.NinePatch;
import com.badlogic.gdx.math.Matrix4;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;
class GraphicsContextTest {
private Camera camera;
private Batch batch;
private BitmapFont font;
private GraphicsContext gc;
@BeforeEach
void setUp() {
camera = mock(Camera.class);
batch = mock(Batch.class);
font = mock(BitmapFont.class);
gc = new GraphicsContext(camera, batch, 600, 800, font);
when(batch.getTransformMatrix()).thenReturn(new Matrix4());
}
@Test
void testDrawNinePatch() {
NinePatch np = mock(NinePatch.class);
gc.draw(np, 10, 20, 100, 200);
verify(np).draw(eq(batch), eq(10f), eq(380f), eq(100f), eq(200f));
}
@Test
void testDrawTexture() {
Texture tex = mock(Texture.class);
when(tex.getHeight()).thenReturn(50);
when(tex.getWidth()).thenReturn(50);
gc.draw(tex, 10, 20);
verify(batch).draw(eq(tex), eq(10f), anyFloat(), anyFloat(), anyFloat(), anyInt(), anyInt(), anyInt(), anyInt(), anyBoolean(), anyBoolean());
}
@Test
void testDrawText() {
gc.draw("test", 10, 20);
verify(font).draw(batch, "test", 10f, 600f - (20f + font.getLineHeight()));
}
@Test
void testSetColour() {
gc.setColour(255, 0, 0);
verify(batch).setColor(1f, 0f, 0f, 1f);
}
@Test
void testTranslate() {
gc.translate(10, 20);
verify(batch).setTransformMatrix(any(Matrix4.class));
}
@Test
void testSaveRestore() {
gc.save();
assertDoesNotThrow(() -> gc.restore());
assertThrows(IllegalStateException.class, () -> gc.restore());
}
}