Compare commits

...

2 Commits

23 changed files with 580 additions and 152 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ The project uses Gradle.
* **Language:** Java * **Language:** Java
* **Dependency Injection:** Uses `avaje-inject`. Components are marked with `@Component` and injected via constructor. * **Dependency Injection:** Uses `avaje-inject`. Components are marked with `@Component` and injected via constructor.
* **Boilerplate:** Uses `lombok` for boilerplate reduction. * **Boilerplate:** Uses `lombok` for boilerplate reduction.
* **Rendering:** Built on LibGDX. Main entry point is `DiceOS.java` which extends `ApplicationAdapter`. * **Rendering:** Built on LibGDX. Main entry point is `DiceOSAdapter.java` which extends `ApplicationAdapter`.
* **Asset Loading:** All assets, including textures, fonts, and NinePatches, must be loaded via the `ResourceLoader` service to ensure proper lifecycle management and caching. * **Asset Loading:** All assets, including textures, fonts, and NinePatches, must be loaded via the `ResourceLoader` service to ensure proper lifecycle management and caching.
* **Project Structure:** * **Project Structure:**
* `src/main/java/be/seeseemelk/diceos/system`: Core OS services (Display, Window, Input, etc.). * `src/main/java/be/seeseemelk/diceos/system`: Core OS services (Display, Window, Input, etc.).
@@ -1,6 +1,7 @@
package be.seeseemelk.diceos; package be.seeseemelk.diceos;
import be.seeseemelk.diceos.system.DiceOS; import be.seeseemelk.diceos.system.DiceOS;
import be.seeseemelk.diceos.system.DiceOSAdapter;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration;
import com.badlogic.gdx.graphics.glutils.HdpiMode; import com.badlogic.gdx.graphics.glutils.HdpiMode;
@@ -13,7 +14,8 @@ public class Bootloader {
config.setTitle("DiceOS"); config.setTitle("DiceOS");
config.setFullscreenMode(Lwjgl3ApplicationConfiguration.getDisplayMode()); config.setFullscreenMode(Lwjgl3ApplicationConfiguration.getDisplayMode());
config.setHdpiMode(HdpiMode.Pixels); config.setHdpiMode(HdpiMode.Pixels);
new Lwjgl3Application(beanScope.get(DiceOS.class), config); beanScope.get(DiceOS.class);
new Lwjgl3Application(beanScope.get(DiceOSAdapter.class), config);
} }
} }
} }
@@ -1,123 +1,36 @@
package be.seeseemelk.diceos.system; package be.seeseemelk.diceos.system;
import be.seeseemelk.diceos.system.gfx.GraphicsContext;
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;
import com.badlogic.gdx.utils.viewport.Viewport;
import io.avaje.inject.Component; import io.avaje.inject.Component;
import io.avaje.inject.PostConstruct;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
@Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class DiceOS extends ApplicationAdapter { @Component
public class DiceOS {
private static DiceOS INSTANCE;
private final ResourceLoader resourceLoader; private final ResourceLoader resourceLoader;
private final DisplayService display;
private final CursorService cursorService;
private final WindowService windowService; private final WindowService windowService;
private final List<OnStartup> startupTasks;
private Texture clouds;
private Texture border;
private Viewport vmViewport;
private Viewport screenViewport;
private int scaling = 1;
private int offsetX = 0;
private int offsetY = 0;
private GraphicsContext gc;
private BitmapFont font; @PostConstruct
void register() {
@Override if (INSTANCE != null) {
public void create() { throw new IllegalStateException("DiceOS already created");
log.info("DiceOS starting..."); }
clouds = resourceLoader.loadTexture("backgrounds/clouds.png"); INSTANCE = this;
border = resourceLoader.loadTexture("system/border.png");
screenViewport = new ScreenViewport();
vmViewport = new ScreenViewport();
vmViewport.update(display.getWidth(), display.getHeight(), true);
for (var task : startupTasks) {
task.onStartup();
} }
font = resourceLoader.loadFont("system/font"); public static DiceOS get() {
if (INSTANCE == null) {
log.info("DiceOS started!"); throw new IllegalStateException("ResourceLoader not initialized yet");
}
return INSTANCE;
} }
@Override public static ResourceLoader getResourceLoader() {
public void resize(int width, int height) { return INSTANCE.resourceLoader;
screenViewport.update(width, height, true);
int displayWidth = display.getWidth();
int displayHeight = display.getHeight();
scaling = Math.min(width / displayWidth, height / displayHeight);
offsetX = (width - (displayWidth * scaling)) / 2;
offsetY = (height - (displayHeight * scaling)) / 2;
cursorService.setScale(scaling);
gc = new GraphicsContext(screenViewport.getCamera(), display.getBatch(), displayHeight, displayWidth);
} }
@Override public static WindowService getWindowService() {
public void render() { return INSTANCE.windowService;
int mouseX = Gdx.input.getX();
int mouseY = display.getHeight() - Gdx.input.getY();
windowService.update(mouseX, mouseY, Gdx.input.isTouched());
cursorService.updateCursor();
ScreenUtils.clear(Color.BLACK);
vmViewport.apply();
display.getScreenBuffer().begin();
display.getBatch().setProjectionMatrix(vmViewport.getCamera().combined);
display.getBatch().begin();
ScreenUtils.clear(Color.GREEN);
// Render background
gc.draw(clouds, 0, 0);
// Render windows
windowService.render(gc);
gc.scissor(0, 0, gc.getWidth(), gc.getHeight(), windowService::paint);
// Render borders
var param = new GraphicsContext.Parameters();
gc.draw(border, 0, 0, param);
param.flipX = true;
gc.draw(border, display.getWidth() - 8, 0, param);
param.flipY = true;
gc.draw(border, display.getWidth() - 8, display.getHeight() - 8, param);
param.flipX = false;
gc.draw(border, 0, display.getHeight() - 8, param);
font.draw(display.getBatch(), "The quick brown fox jumps over the lazy dog", 6, display.getHeight() - 12);
// Finish rendering to screen buffer
display.getBatch().end();
display.getScreenBuffer().end();
// Render screen buffer to actual screen
screenViewport.apply();
display.getBatch().setProjectionMatrix(screenViewport.getCamera().combined);
display.getBatch().begin();
var texture = display.getScreenBuffer().getColorBufferTexture();
var textureRegion = new TextureRegion(texture);
textureRegion.flip(false, true);
display.getBatch().draw(textureRegion, offsetX, offsetY, display.getWidth() * scaling, display.getHeight() * scaling);
display.getBatch().end();
} }
} }
@@ -0,0 +1,121 @@
package be.seeseemelk.diceos.system;
import be.seeseemelk.diceos.system.gfx.GraphicsContext;
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;
import com.badlogic.gdx.utils.viewport.Viewport;
import io.avaje.inject.Component;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
@Component
@RequiredArgsConstructor
public class DiceOSAdapter extends ApplicationAdapter {
private final ResourceLoader resourceLoader;
private final DisplayService display;
private final CursorService cursorService;
private final WindowService windowService;
private final List<OnStartup> startupTasks;
private Texture clouds;
private Texture border;
private Viewport vmViewport;
private Viewport screenViewport;
private int scaling = 1;
private int offsetX = 0;
private int offsetY = 0;
private GraphicsContext gc;
private BitmapFont font;
@Override
public void create() {
log.info("DiceOS starting...");
clouds = resourceLoader.loadTexture("backgrounds/clouds.png");
border = resourceLoader.loadTexture("system/border.png");
screenViewport = new ScreenViewport();
vmViewport = new ScreenViewport();
vmViewport.update(display.getWidth(), display.getHeight(), true);
for (var task : startupTasks) {
task.onStartup();
}
font = resourceLoader.loadFont("system/font");
log.info("DiceOS started!");
}
@Override
public void resize(int width, int height) {
screenViewport.update(width, height, true);
int displayWidth = display.getWidth();
int displayHeight = display.getHeight();
scaling = Math.min(width / displayWidth, height / displayHeight);
offsetX = (width - (displayWidth * scaling)) / 2;
offsetY = (height - (displayHeight * scaling)) / 2;
cursorService.setScale(scaling);
gc = new GraphicsContext(screenViewport.getCamera(), display.getBatch(), displayHeight, displayWidth, font);
}
@Override
public void render() {
int mouseX = Gdx.input.getX();
int mouseY = display.getHeight() - Gdx.input.getY();
windowService.update(mouseX, mouseY, Gdx.input.isTouched());
cursorService.updateCursor();
ScreenUtils.clear(Color.BLACK);
vmViewport.apply();
display.getScreenBuffer().begin();
display.getBatch().setProjectionMatrix(vmViewport.getCamera().combined);
display.getBatch().begin();
ScreenUtils.clear(Color.GREEN);
// Render background
gc.draw(clouds, 0, 0);
// Render windows
windowService.paint(gc);
// Render borders
var param = new GraphicsContext.Parameters();
gc.draw(border, 0, 0, param);
param.flipX = true;
gc.draw(border, display.getWidth() - 8, 0, param);
param.flipY = true;
gc.draw(border, display.getWidth() - 8, display.getHeight() - 8, param);
param.flipX = false;
gc.draw(border, 0, display.getHeight() - 8, param);
// Finish rendering to screen buffer
display.getBatch().end();
display.getScreenBuffer().end();
// Render screen buffer to actual screen
screenViewport.apply();
display.getBatch().setProjectionMatrix(screenViewport.getCamera().combined);
display.getBatch().begin();
var texture = display.getScreenBuffer().getColorBufferTexture();
var textureRegion = new TextureRegion(texture);
textureRegion.flip(false, true);
display.getBatch().draw(textureRegion, offsetX, offsetY, display.getWidth() * scaling, display.getHeight() * scaling);
display.getBatch().end();
}
}
@@ -1,8 +1,8 @@
package be.seeseemelk.diceos.system; package be.seeseemelk.diceos.system;
import be.seeseemelk.diceos.system.gfx.GraphicsContext; import be.seeseemelk.diceos.system.gfx.GraphicsContext;
import be.seeseemelk.diceos.system.toolkit.Menubar;
import be.seeseemelk.diceos.system.toolkit.Window; import be.seeseemelk.diceos.system.toolkit.Window;
import be.seeseemelk.diceos.system.toolkit.menu.Menu;
import com.badlogic.gdx.graphics.g2d.NinePatch; import com.badlogic.gdx.graphics.g2d.NinePatch;
import io.avaje.inject.Component; import io.avaje.inject.Component;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -16,21 +16,37 @@ public class WindowService implements OnStartup {
private final ResourceLoader resourceLoader; private final ResourceLoader resourceLoader;
private final List<Window> windows = new ArrayList<>(); private final List<Window> windows = new ArrayList<>();
private NinePatch menubar; private NinePatch menubar;
private NinePatch windowDecoration; private Menu menu;
private Menubar systemMenubar;
@Override @Override
public void onStartup() { public void onStartup() {
menubar = resourceLoader.loadNinePatch("system/menubar.png"); menubar = resourceLoader.loadNinePatch("system/menubar.png");
windowDecoration = resourceLoader.loadNinePatch("system/window.png");
systemMenubar = new Menubar() menu = new Menu("Root");
.addItem("Dice") menu.add(new Menu("Dice"));
.addItem("System"); menu.add(new Menu("System"));
} }
public void spawnWindow(int x, int y, int width, int height) { public void spawnWindow(int x, int y, int width, int height) {
windows.add(new Window(x, y, width, height, windowDecoration)); windows.add(new Window(x, y, width, height, Window.Style.NORMAL));
}
/**
* Maps a window to be managed by this service.
* @param window The window to manage.
*/
public void map(Window window) {
if (!windows.contains(window)) {
windows.add(window);
}
}
/**
* Unmaps a window, removing it from management.
* @param window The window to remove.
*/
public void unmap(Window window) {
windows.remove(window);
} }
public void update(int mouseX, int mouseY, boolean isTouched) { public void update(int mouseX, int mouseY, boolean isTouched) {
@@ -39,14 +55,21 @@ public class WindowService implements OnStartup {
} }
} }
public void render(GraphicsContext gc) { public void paint(GraphicsContext gc) {
for (Window window : windows) { for (Window window : windows) {
window.paint(gc); window.paint(gc);
} }
}
void paint(GraphicsContext gc) {
// Render menubar // Render menubar
gc.draw(menubar, 0, 0, gc.getWidth(), 14); gc.draw(menubar, 0, 0, gc.getWidth(), 14);
// Render menubar items
gc.save();
gc.translate(5, 0);
for (var submenu : menu.getItems()) {
submenu.paint(gc);
gc.translate(submenu.getWidth(), 0);
}
gc.restore();
} }
} }
@@ -27,6 +27,7 @@ public class FontLoader {
var region = new TextureRegion(texture); var region = new TextureRegion(texture);
var data = new BitmapFont.BitmapFontData(); var data = new BitmapFont.BitmapFontData();
data.lineHeight = metadata.getGeneral().getLineHeight();
var glyphMap = new Map2D<Integer, Integer, BitmapFont.Glyph>(); var glyphMap = new Map2D<Integer, Integer, BitmapFont.Glyph>();
var glyphLines = metadata.getGeneral().getOrder().split("\n"); var glyphLines = metadata.getGeneral().getOrder().split("\n");
@@ -65,6 +66,8 @@ public class FontLoader {
glyph.u2 = (float)widthTiming.glyphEnd / pixmap.getWidth(); glyph.u2 = (float)widthTiming.glyphEnd / pixmap.getWidth();
glyph.v = (float)heightTiming.glyphStart / pixmap.getHeight(); glyph.v = (float)heightTiming.glyphStart / pixmap.getHeight();
glyph.v2 = (float)heightTiming.glyphEnd / pixmap.getHeight(); glyph.v2 = (float)heightTiming.glyphEnd / pixmap.getHeight();
data.capHeight = Math.max(data.capHeight, heightTiming.getGlyphHeight());
} }
} }
@@ -15,4 +15,7 @@ public class GeneralSection {
* The order of the glyphs in the image file. * The order of the glyphs in the image file.
*/ */
private String order; private String order;
// @gemini implement
private int lineHeight;
} }
@@ -1,16 +1,16 @@
package be.seeseemelk.diceos.system.gfx; package be.seeseemelk.diceos.system.gfx;
import be.seeseemelk.diceos.system.DiceOS;
import com.badlogic.gdx.graphics.Camera; import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch; 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.graphics.g2d.NinePatch;
import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack; import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import java.util.function.Consumer;
/** /**
* Graphics context and rendering utilities. * Graphics context and rendering utilities.
*/ */
@@ -21,15 +21,49 @@ public class GraphicsContext {
private final Batch batch; private final Batch batch;
private final int height; private final int height;
private final int width; private final int width;
private final BitmapFont font;
private final java.util.Stack<GraphicsState> stateStack = new java.util.Stack<>();
private static class GraphicsState {
private final com.badlogic.gdx.math.Matrix4 transform;
private final boolean scissorEnabled;
public GraphicsState(com.badlogic.gdx.math.Matrix4 transform, boolean scissorEnabled) {
this.transform = transform.cpy();
this.scissorEnabled = scissorEnabled;
}
}
/**
* Draws a NinePatch image at the specified coordinates and dimensions.
* @param image The NinePatch to draw.
* @param x The x-coordinate.
* @param y The y-coordinate.
* @param width The width.
* @param height The height.
*/
public void draw(NinePatch image, int x, int y, int width, int height) { public void draw(NinePatch image, int x, int y, int width, int height) {
image.draw(batch, x, getHeight() - height - y, width, height); image.draw(batch, x, getHeight() - height - y, width, height);
} }
/**
* Draws a texture at the specified coordinates.
* @param texture The texture to draw.
* @param x The x-coordinate.
* @param y The y-coordinate.
*/
public void draw(Texture texture, int x, int y) { public void draw(Texture texture, int x, int y) {
draw(texture, x, y, Parameters.DEFAULT); draw(texture, x, y, Parameters.DEFAULT);
} }
/**
* Draws a texture with specific drawing parameters.
* @param texture The texture to draw.
* @param x The x-coordinate.
* @param y The y-coordinate.
* @param params Drawing parameters (flipX, flipY).
*/
public void draw(Texture texture, int x, int y, Parameters params) { public void draw(Texture texture, int x, int y, Parameters params) {
batch.draw(texture, batch.draw(texture,
x, getHeight() - y - texture.getHeight(), texture.getWidth(), texture.getHeight(), x, getHeight() - y - texture.getHeight(), texture.getWidth(), texture.getHeight(),
@@ -37,22 +71,93 @@ public class GraphicsContext {
params.flipX, params.flipY); params.flipX, params.flipY);
} }
public void scissor(int x, int y, int width, int height, Consumer<GraphicsContext> callback) { /**
var scissors = new Rectangle(); * Draws some text with the current font.
var clipBounds = new Rectangle(x, getHeight() - y - height, width, height); * @param text The text to draw.
ScissorStack.calculateScissors(camera, batch.getTransformMatrix(), clipBounds, scissors); * @param x The x coordinate to draw at.
batch.flush(); * @param y The y coordinate to draw at.
if (ScissorStack.pushScissors(scissors)) { */
callback.accept(this); public void draw(String text, int x, int y) {
batch.flush(); font.draw(batch, text, x, getHeight() - (y + font.getLineHeight()));
ScissorStack.popScissors();
}
} }
/**
* Fills a rectangular area with the currently set color.
* @param x The x-coordinate.
* @param y The y-coordinate.
* @param width The width.
* @param height The height.
*/
public void fillRect(int x, int y, int width, int height) {
var texture = DiceOS.getResourceLoader().loadTexture("system/white.png");
batch.draw(texture, x, getHeight() - height - y, width, height); // This is not ideal.
}
/**
* Sets the color for subsequent drawing operations.
* @param r The red component (0-255).
* @param g The green component (0-255).
* @param b The blue component (0-255).
*/
public void setColour(int r, int g, int b) {
batch.setColor(r / 255f, g / 255f, b / 255f, 1f);
}
/**
* Shifts the context to the right and down by some amount.
* Negative values move towards the opposite direction.
*
* @param x The number of pixels to move to the right by.
* @param y The number of pixels to move down by.
*/
public void translate(int x, int y) {
batch.setTransformMatrix(batch.getTransformMatrix().trn(x, -y, 0));
}
/**
* Sets a clipping rectangle for subsequent draw operations.
*
* @param width The width of the clipping area.
* @param height The height of the clipping area.
*/
public void clip(int width, int height) {
var scissors = new Rectangle(0, 0, width, height);
ScissorStack.calculateScissors(camera, batch.getTransformMatrix(), scissors, scissors);
batch.flush();
ScissorStack.pushScissors(scissors);
}
/**
* Pushes the current state onto an internal stack.
* The state can later be restored using {@link #restore()}.
*/
public void save() {
stateStack.push(new GraphicsState(batch.getTransformMatrix(), false));
}
/**
* Restores a state pushed by {@link #save()}
* @throws IllegalStateException if no state was pushed.
*/
public void restore() {
if (stateStack.isEmpty()) {
throw new IllegalStateException("No state to restore");
}
batch.flush();
GraphicsState state = stateStack.pop();
batch.setTransformMatrix(state.transform);
}
/**
* Graphics drawing parameters.
*/
public static class Parameters { public static class Parameters {
public final static Parameters DEFAULT = new Parameters(); public final static Parameters DEFAULT = new Parameters();
/** Indicates if the x-axis should be flipped. */
public boolean flipX = false; public boolean flipX = false;
/** Indicates if the y-axis should be flipped. */
public boolean flipY = false; public boolean flipY = false;
} }
} }
@@ -1,7 +1,40 @@
package be.seeseemelk.diceos.system.toolkit; package be.seeseemelk.diceos.system.toolkit;
import be.seeseemelk.diceos.system.gfx.GraphicsContext; import be.seeseemelk.diceos.system.gfx.GraphicsContext;
import be.seeseemelk.diceos.system.toolkit.events.Event;
import be.seeseemelk.diceos.system.toolkit.events.InputEvent;
import be.seeseemelk.diceos.system.toolkit.events.MouseEvent;
public abstract class Component { public abstract class Component {
public abstract int getX();
public abstract int getY();
public abstract int getWidth();
public abstract int getHeight();
public abstract void paint(GraphicsContext gc); public abstract void paint(GraphicsContext gc);
public void onEvent(Event event) {
switch (event) {
case InputEvent inputEvent -> onInputEvent(inputEvent);
default -> {}
}
}
public void onInputEvent(InputEvent event) {
switch (event) {
case MouseEvent mouseEvent -> onMouseEvent(mouseEvent);
default -> {}
}
}
public void onMouseEvent(MouseEvent event) {
switch (event) {
case be.seeseemelk.diceos.system.toolkit.events.MouseClickEvent mouseClickEvent -> onMouseClick(mouseClickEvent);
default -> {}
}
}
public void onMouseClick(be.seeseemelk.diceos.system.toolkit.events.MouseClickEvent event) {
// Default empty implementation
}
} }
@@ -1,6 +1,7 @@
package be.seeseemelk.diceos.system.toolkit; package be.seeseemelk.diceos.system.toolkit;
import be.seeseemelk.diceos.system.gfx.GraphicsContext; import be.seeseemelk.diceos.system.gfx.GraphicsContext;
import be.seeseemelk.diceos.system.toolkit.events.MouseEvent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -22,4 +23,14 @@ public abstract class Container extends Component {
component.paint(gc); component.paint(gc);
} }
} }
@Override
public void onMouseEvent(MouseEvent event) {
super.onMouseEvent(event);
for (var component : components) {
if (component instanceof Window window && window.isHovered(event.getX(), event.getY())) {
component.onMouseEvent(event);
}
}
}
} }
@@ -1,16 +0,0 @@
package be.seeseemelk.diceos.system.toolkit;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
public class Menubar {
@Getter
private final List<String> items = new ArrayList<>();
public Menubar addItem(String item) {
items.add(item);
return this;
}
}
@@ -1,24 +1,65 @@
package be.seeseemelk.diceos.system.toolkit; package be.seeseemelk.diceos.system.toolkit;
import be.seeseemelk.diceos.system.DiceOS;
import be.seeseemelk.diceos.system.gfx.GraphicsContext; import be.seeseemelk.diceos.system.gfx.GraphicsContext;
import com.badlogic.gdx.graphics.g2d.NinePatch; import com.badlogic.gdx.graphics.g2d.NinePatch;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
/**
* Represents a window within the system.
*/
@Setter @Setter
@Getter @Getter
public class Window extends Container { public class Window extends Container {
private String title; private String title;
private int x, y, width, height; private int x, y, width, height;
private boolean active; private boolean active;
private NinePatch decoration; private NinePatch borders;
public Window(int x, int y, int width, int height, NinePatch decoration) { /**
* The style of window decorations to use.
*/
public enum Style {
/**
* The standard style with a full window border.
* This is the default.
*/
NORMAL,
/**
* No borders
*/
NONE,
}
public Window(int x, int y, int width, int height, Style style) {
this.x = x; this.x = x;
this.y = y; this.y = y;
this.width = width; this.width = width;
this.height = height; this.height = height;
this.decoration = decoration; setStyle(style);
}
public void setStyle(Style style) {
switch (style) {
case NORMAL -> borders = DiceOS.getResourceLoader().loadNinePatch("system/window.png");
case NONE -> borders = null;
}
}
/**
* Shows this window in the system.
*/
public void show() {
DiceOS.getWindowService().map(this);
}
/**
* Hides this window from the system.
*/
public void hide() {
DiceOS.getWindowService().unmap(this);
} }
public boolean isHovered(int mouseX, int mouseY) { public boolean isHovered(int mouseX, int mouseY) {
@@ -47,8 +88,8 @@ public class Window extends Container {
@Override @Override
public void paint(GraphicsContext gc) { public void paint(GraphicsContext gc) {
if (decoration != null) { if (borders != null) {
decoration.draw(gc.getBatch(), x, y, width, height); borders.draw(gc.getBatch(), x, gc.getHeight() - height - y, width, height);
} }
super.paint(gc); super.paint(gc);
} }
@@ -0,0 +1,4 @@
package be.seeseemelk.diceos.system.toolkit.events;
public abstract class Event {
}
@@ -0,0 +1,4 @@
package be.seeseemelk.diceos.system.toolkit.events;
public abstract class InputEvent extends Event {
}
@@ -0,0 +1,25 @@
package be.seeseemelk.diceos.system.toolkit.events;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Event representing a mouse button click.
*/
@Getter
@RequiredArgsConstructor
public class MouseClickEvent extends MouseEvent {
private final int x;
private final int y;
/**
* The mouse button that was clicked.
*/
private final Button button;
public enum Button {
LEFT,
RIGHT,
;
}
}
@@ -0,0 +1,16 @@
package be.seeseemelk.diceos.system.toolkit.events;
/**
* Base class for all mouse-related input events.
*/
public abstract class MouseEvent extends InputEvent {
/**
* @return The x-coordinate of the mouse event.
*/
public abstract int getX();
/**
* @return The y-coordinate of the mouse event.
*/
public abstract int getY();
}
@@ -0,0 +1,53 @@
package be.seeseemelk.diceos.system.toolkit.menu;
import be.seeseemelk.diceos.system.DiceOS;
import be.seeseemelk.diceos.system.gfx.GraphicsContext;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@RequiredArgsConstructor
@Getter
public class Menu implements MenuItem {
private final String title;
private final List<MenuItem> items = new ArrayList<>();
public Menu add(MenuItem item) {
items.add(item);
return this;
}
public Menu remove(MenuItem item) {
items.remove(item);
return this;
}
@Override
public int getWidth() {
return items.stream().mapToInt(MenuItem::getWidth).max().orElse(0);
}
@Override
public int getHeight() {
return items.stream().mapToInt(MenuItem::getHeight).sum();
}
/**
* Opens the popup at a specific coordinate.
*
* @param x The x-coordinate.
* @param y The y-coordinate.
* @return The created MenuPopup.
*/
public MenuPopup show(int x, int y) {
return new MenuPopup(this, x, y);
}
@Override
public void paint(GraphicsContext gc) {
// Renders the menu as a simple menu button
gc.draw(title, 0, 5);
}
}
@@ -0,0 +1,41 @@
package be.seeseemelk.diceos.system.toolkit.menu;
import be.seeseemelk.diceos.system.gfx.GraphicsContext;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class MenuButton implements MenuItem {
private String text;
private Runnable callback;
public MenuButton(String text) {
this.text = text;
}
public void onClick(Runnable callback) {
this.callback = callback;
}
public void click() {
if (callback != null) {
callback.run();
}
}
@Override
public int getWidth() {
return text.length() * 8 + 10; // Simple approximation for now
}
@Override
public int getHeight() {
return 16;
}
@Override
public void paint(GraphicsContext gc) {
// Implement painting logic
}
}
@@ -0,0 +1,20 @@
package be.seeseemelk.diceos.system.toolkit.menu;
import be.seeseemelk.diceos.system.gfx.GraphicsContext;
public class MenuDivider implements MenuItem {
@Override
public int getWidth() {
return 100;
}
@Override
public int getHeight() {
return 4;
}
@Override
public void paint(GraphicsContext gc) {
// Implement divider drawing
}
}
@@ -0,0 +1,9 @@
package be.seeseemelk.diceos.system.toolkit.menu;
import be.seeseemelk.diceos.system.gfx.GraphicsContext;
public interface MenuItem {
int getWidth();
int getHeight();
void paint(GraphicsContext gc);
}
@@ -0,0 +1,16 @@
package be.seeseemelk.diceos.system.toolkit.menu;
import be.seeseemelk.diceos.system.toolkit.Window;
/**
* A visisble menu popup.
*/
public class MenuPopup {
private final Menu menu;
private final Window window;
public MenuPopup(Menu menu, int x, int y) {
this.menu = menu;
this.window = new Window(x, y, menu.getWidth(), menu.getHeight(), Window.Style.NONE);
}
}
+1
View File
@@ -4,6 +4,7 @@ order = """
aAbBcCdDeEfFgGhHiIjJkKlLmMnN aAbBcCdDeEfFgGhHiIjJkKlLmMnN
oOpPqQrRsStTuUvVwWxXyYzZ oOpPqQrRsStTuUvVwWxXyYzZ
""" """
lineHeight = 7
[padding] [padding]
horizontal = 1 horizontal = 1
Binary file not shown.

After

Width:  |  Height:  |  Size: 82 B