diff --git a/build.gradle.kts b/build.gradle.kts index 614409d..fd2db84 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,6 +34,9 @@ dependencies { testImplementation("io.avaje:avaje-inject-test:${avajeInjectVersion}") testAnnotationProcessor("io.avaje:avaje-inject-generator:${avajeInjectVersion}") + // Parsing TOML files + implementation("tools.jackson.dataformat:jackson-dataformat-toml:3.0.2") + // LibGDX api("com.badlogicgames.gdx:gdx:$gdxVersion") api("com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion") diff --git a/src/main/java/be/seeseemelk/diceos/system/DiceOS.java b/src/main/java/be/seeseemelk/diceos/system/DiceOS.java index 403f97f..0a88551 100644 --- a/src/main/java/be/seeseemelk/diceos/system/DiceOS.java +++ b/src/main/java/be/seeseemelk/diceos/system/DiceOS.java @@ -45,7 +45,6 @@ public class DiceOS extends ApplicationAdapter { vmViewport = new ScreenViewport(); vmViewport.update(display.getWidth(), display.getHeight(), true); - for (var task : startupTasks) { task.onStartup(); } @@ -83,22 +82,21 @@ public class DiceOS extends ApplicationAdapter { ScreenUtils.clear(Color.GREEN); // Render background - display.draw(clouds, 0, 0); + gc.draw(clouds, 0, 0); windowService.paint(gc); // Render borders - var param = new DisplayService.Parameters(); - display.draw(border, 0, 0, param); + var param = new GraphicsContext.Parameters(); + gc.draw(border, 0, 0, param); param.flipX = true; - display.draw(border, display.getWidth() - 8, 0, param); + gc.draw(border, display.getWidth() - 8, 0, param); param.flipY = true; - display.draw(border, display.getWidth() - 8, display.getHeight() - 8, param); + gc.draw(border, display.getWidth() - 8, display.getHeight() - 8, param); param.flipX = false; - display.draw(border, 0, display.getHeight() - 8, param); + gc.draw(border, 0, display.getHeight() - 8, param); - font.setColor(1, 1, 1, 0.5f); - font.draw(display.getBatch(), "DiceOS", 50, display.getHeight() - 40); + font.draw(display.getBatch(), "This is DiceOS", 6, display.getHeight() - 12); // Finish rendering to screen buffer display.getBatch().end(); diff --git a/src/main/java/be/seeseemelk/diceos/system/DisplayService.java b/src/main/java/be/seeseemelk/diceos/system/DisplayService.java index fddb670..3f52569 100644 --- a/src/main/java/be/seeseemelk/diceos/system/DisplayService.java +++ b/src/main/java/be/seeseemelk/diceos/system/DisplayService.java @@ -41,22 +41,4 @@ public class DisplayService implements OnStartup { public int getHeight() { return 360; } - - public void draw(Texture texture, int x, int y) { - draw(texture, x, y, Parameters.DEFAULT); - } - - public void draw(Texture texture, int x, int y, Parameters params) { - batch.draw(texture, - x, getHeight() - y - texture.getHeight(), texture.getWidth(), texture.getHeight(), - 0, 0, texture.getWidth(), texture.getHeight(), - params.flipX, params.flipY); - } - - public static class Parameters { - public final static Parameters DEFAULT = new Parameters(); - - public boolean flipX = false; - public boolean flipY = false; - } } diff --git a/src/main/java/be/seeseemelk/diceos/system/ResourceLoader.java b/src/main/java/be/seeseemelk/diceos/system/ResourceLoader.java index 9a257f8..58143d4 100644 --- a/src/main/java/be/seeseemelk/diceos/system/ResourceLoader.java +++ b/src/main/java/be/seeseemelk/diceos/system/ResourceLoader.java @@ -1,15 +1,19 @@ package be.seeseemelk.diceos.system; -import be.seeseemelk.diceos.system.utils.Map2D; +import be.seeseemelk.diceos.system.font.FontLoader; +import be.seeseemelk.diceos.system.font.FontMetadata; +import be.seeseemelk.diceos.system.gfx.NinePatchLoader; import com.badlogic.gdx.Gdx; 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.TextureRegion; +import com.badlogic.gdx.graphics.g2d.NinePatch; import io.avaje.inject.Component; import lombok.Cleanup; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.dataformat.toml.TomlFactory; import java.util.HashMap; import java.util.Map; @@ -20,7 +24,9 @@ import java.util.Map; public class ResourceLoader { private final Map pixmaps = new HashMap<>(); private final Map textures = new HashMap<>(); + private final Map ninePatches = new HashMap<>(); private final Map fonts = new HashMap<>(); + private final ObjectMapper mapper = new ObjectMapper(new TomlFactory()); public Pixmap loadPixmap(String path) { return pixmaps.computeIfAbsent(path, p -> new Pixmap(Gdx.files.internal(p))); @@ -30,6 +36,14 @@ public class ResourceLoader { return textures.computeIfAbsent(path, p -> new Texture(Gdx.files.internal(p))); } + public NinePatch loadNinePatch(String path) { + return ninePatches.computeIfAbsent(path, p -> { + var pixmap = loadPixmap(p); + var loader = new NinePatchLoader(pixmap); + return loader.getNinePatch(); + }); + } + public BitmapFont loadFont(String path) { return fonts.computeIfAbsent(path, this::loadBitmapfont); } @@ -37,186 +51,9 @@ public class ResourceLoader { private BitmapFont loadBitmapfont(String path) { log.info("Loading bitmap font from {}", path); - @Cleanup("dispose") var pixmap = loadPixmap(path + ".png"); - var texture = new Texture(pixmap); - var region = new TextureRegion(texture); - - var data = new BitmapFont.BitmapFontData(); - - var glyphMap = new Map2D(); - var glyphLines = Gdx.files.internal(path + ".txt").readString().split("\n"); - for (var y = 0; y < glyphLines.length; y++) { - var line = glyphLines[y].toCharArray(); - for (var x = 0; x < line.length; x++) { - var glyph = new BitmapFont.Glyph(); - glyph.id = line[x]; - glyphMap.put(x, y, glyph); - data.setGlyph(glyph.id, glyph); - } - } - - // Read the vertical timing lines to find the horizontal ones and read glyph heights - VerticalTiming[] glyphHeights = readGlyphHeights(pixmap, glyphLines.length); - // Read the horizontal timing lines to read glyph widths - HorizontalTiming[][] glyphWidths = new HorizontalTiming[glyphLines.length][]; - for (var y = 0; y < glyphHeights.length; y++) { - glyphWidths[y] = readHorizontalTimings(pixmap, glyphLines[y].length(), glyphHeights[y].timingLine); - } - - // Assign glyph sizes - for (var y = 0; y < glyphLines.length; y++) { - for (var x = 0; x < glyphLines[y].length(); x++) { - var glyph = glyphMap.get(x, y); - var widthTiming = glyphWidths[y][x]; - var heightTiming = glyphHeights[y]; - glyph.width = widthTiming.getGlyphWidth(); - glyph.height = heightTiming.getGlyphHeight(); - glyph.srcX = widthTiming.glyphStart; - glyph.srcY = heightTiming.glyphStart; - glyph.xoffset = 0; - glyph.yoffset = 0; - glyph.xadvance = glyph.width; - glyph.u = (float)widthTiming.glyphStart / pixmap.getWidth(); - glyph.u2 = (float)widthTiming.glyphEnd / pixmap.getWidth(); - glyph.v = (float)heightTiming.glyphStart / pixmap.getHeight(); - glyph.v2 = (float)heightTiming.glyphEnd / pixmap.getHeight(); - } - } - - return new BitmapFont(data, region, true); - } - - /** - * Reads the vertical timing line from the pixmap. - *

- * The timing line is a 1 pixel wide line on the left side of the pixmap that indicate the positions of the - * horizontal timing lines, and the heights of the glyphs. - *

- * A white pixel on the timing line indicates the line is unused, a black indicates either a timing line, or the - * height of a glyph. - *

- * It is expected that the line ends with a white pixel. - * - * @param pixmap The pixmap containing the timing line. - * @param glyphCountY The number of glyphs in the Y direction. - * @return An array of glyph heights. - */ - private VerticalTiming[] readGlyphHeights(Pixmap pixmap, int glyphCountY) { - log.info("Reading vertical timings for {} glyphs", glyphCountY); - - enum State { - SEARCHING_FOR_LINE, - WAITING_FOR_GLYPH_HEIGHTS, - READING_GLYPH_HEIGHTS - } - State state = State.SEARCHING_FOR_LINE; - - VerticalTiming[] glyphHeights = new VerticalTiming[glyphCountY]; - VerticalTiming currentTiming = null; - int currentGlyphLine = 0; - int y = 0; - while (y < pixmap.getHeight()) { - boolean isBlack = pixmap.getPixel(0, y) == 0x000000ff; - switch (state) { - case SEARCHING_FOR_LINE -> { - if (isBlack) { - currentTiming = new VerticalTiming(); - glyphHeights[currentGlyphLine++] = currentTiming; - currentTiming.timingLine = y; - state = State.WAITING_FOR_GLYPH_HEIGHTS; - } - } - case WAITING_FOR_GLYPH_HEIGHTS -> { - if (isBlack) { - currentTiming.glyphStart = y; - state = State.READING_GLYPH_HEIGHTS; - } - } - case READING_GLYPH_HEIGHTS -> { - if (!isBlack) { - currentTiming.glyphEnd = y; - state = State.SEARCHING_FOR_LINE; - log.info("Found glyph height: {} (from {} to {})", currentTiming.getGlyphHeight(), currentTiming.glyphStart, currentTiming.glyphEnd); - } - } - } - y++; - } - - return glyphHeights; - } - - private static class VerticalTiming { - /// The Y position of the horizontal timing line. - int timingLine; - /// The start Y position of the glyph line. - int glyphStart; - /// The end Y position of the glyph line. - int glyphEnd; - - public int getGlyphHeight() { - return glyphEnd - glyphStart; - } - } - - /** - * Reads the horizontal timing lines from the pixmap. - *

- * The timing lines are 1 pixel high lines and can occur at any Y position in the pixmap. - * The Y position is indicated through the vertical timing. - *

- * A white pixel on the timing line indicates the line is unused, a line of black pixels indicates the width of a glyph. - * @param pixmap The pixmap containing the timing lines. - * @param glyphCountX The number of glyphs in the X direction. - * @param timingLineY The Y position of the timing line to read. - * @return An array of glyph widths. - */ - private HorizontalTiming[] readHorizontalTimings(Pixmap pixmap, int glyphCountX, int timingLineY) { - log.info("Reading horizontal timings for {} glyphs at y={}", glyphCountX, timingLineY); - - enum State { - WAITING_FOR_GLYPH_WIDTHS, - READING_GLYPH_WIDTHS - } - State state = State.WAITING_FOR_GLYPH_WIDTHS; - - HorizontalTiming[] glyphWidths = new HorizontalTiming[glyphCountX]; - HorizontalTiming currentTiming = null; - int currentGlyphIndex = 0; - int x = 1; - while (x < pixmap.getWidth()) { - boolean isBlack = pixmap.getPixel(x, timingLineY) == 0x000000ff; - switch (state) { - case WAITING_FOR_GLYPH_WIDTHS -> { - if (isBlack) { - currentTiming = new HorizontalTiming(); - glyphWidths[currentGlyphIndex++] = currentTiming; - currentTiming.glyphStart = x; - state = State.READING_GLYPH_WIDTHS; - } - } - case READING_GLYPH_WIDTHS -> { - if (!isBlack) { - currentTiming.glyphEnd = x; - state = State.WAITING_FOR_GLYPH_WIDTHS; - log.info("Found glyph width: {} (from {} to {})", currentTiming.getGlyphWidth(), currentTiming.glyphStart, currentTiming.glyphEnd); - } - } - } - x++; - } - - return glyphWidths; - } - - private static class HorizontalTiming { - /// The X position of the vertical timing line. - int glyphStart; - /// The end X position of the vertical timing line. - int glyphEnd; - - public int getGlyphWidth() { - return glyphEnd - glyphStart; - } + var metadataString = Gdx.files.internal(path + ".toml").readString(); + var metadata = mapper.readValue(metadataString, FontMetadata.class); + @Cleanup("dispose") var pixmap = loadPixmap(metadata.getGeneral().getSource()); + return FontLoader.loadBitmapfont(pixmap, metadata); } } diff --git a/src/main/java/be/seeseemelk/diceos/system/WindowService.java b/src/main/java/be/seeseemelk/diceos/system/WindowService.java index c74236f..cb8ae0c 100644 --- a/src/main/java/be/seeseemelk/diceos/system/WindowService.java +++ b/src/main/java/be/seeseemelk/diceos/system/WindowService.java @@ -15,8 +15,7 @@ public class WindowService implements OnStartup { @Override public void onStartup() { - var texture = resourceLoader.loadTexture("system/menubar.png"); - menubar = new NinePatch(texture, 9, 9, 15, 1); + menubar = resourceLoader.loadNinePatch("system/menubar.png"); systemMenubar = new Menubar() .addItem("Dice") @@ -25,6 +24,6 @@ public class WindowService implements OnStartup { void paint(GraphicsContext gc) { // Render menubar - menubar.draw(gc.getBatch(), 0, gc.getHeight() - 16, gc.getWidth(), 16); + gc.draw(menubar, 0, 0, gc.getWidth(), 13); } } diff --git a/src/main/java/be/seeseemelk/diceos/system/font/FontLoader.java b/src/main/java/be/seeseemelk/diceos/system/font/FontLoader.java new file mode 100644 index 0000000..4ab7f5b --- /dev/null +++ b/src/main/java/be/seeseemelk/diceos/system/font/FontLoader.java @@ -0,0 +1,215 @@ +package be.seeseemelk.diceos.system.font; + +import be.seeseemelk.diceos.system.utils.Map2D; +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.TextureRegion; +import lombok.experimental.UtilityClass; +import lombok.extern.slf4j.Slf4j; + +/** + * Loads bitmap fonts from a TOML file and a PNG file containing timing information. + *

+ * This class should not be used directly. Instead, use the {@link be.seeseemelk.diceos.system.ResourceLoader}. + */ +@UtilityClass +@Slf4j +public class FontLoader { + /** + * Loads a bitmap font from a pixmap and metadata. + * @param pixmap The pixmap containing the font texture and timing information. + * @param metadata The metadata in TOML format. + * @return The loaded bitmap font. + */ + public static BitmapFont loadBitmapfont(Pixmap pixmap, FontMetadata metadata) { + var texture = new Texture(pixmap); + var region = new TextureRegion(texture); + + var data = new BitmapFont.BitmapFontData(); + + var glyphMap = new Map2D(); + var glyphLines = metadata.getGeneral().getOrder().split("\n"); + for (var y = 0; y < glyphLines.length; y++) { + var line = glyphLines[y].toCharArray(); + for (var x = 0; x < line.length; x++) { + var glyph = new BitmapFont.Glyph(); + glyph.id = line[x]; + glyphMap.put(x, y, glyph); + data.setGlyph(glyph.id, glyph); + } + } + + // Read the vertical timing lines to find the horizontal ones and read glyph heights + VerticalTiming[] glyphHeights = readGlyphHeights(pixmap, glyphLines.length); + // Read the horizontal timing lines to read glyph widths + HorizontalTiming[][] glyphWidths = new HorizontalTiming[glyphLines.length][]; + for (var y = 0; y < glyphHeights.length; y++) { + glyphWidths[y] = readHorizontalTimings(pixmap, glyphLines[y].length(), glyphHeights[y].timingLine); + } + + // Assign glyph sizes + for (var y = 0; y < glyphLines.length; y++) { + for (var x = 0; x < glyphLines[y].length(); x++) { + var glyph = glyphMap.get(x, y); + var widthTiming = glyphWidths[y][x]; + var heightTiming = glyphHeights[y]; + glyph.width = widthTiming.getGlyphWidth(); + glyph.height = heightTiming.getGlyphHeight(); + glyph.srcX = widthTiming.glyphStart; + glyph.srcY = heightTiming.glyphStart; + glyph.xoffset = 0; + glyph.yoffset = 0; + glyph.xadvance = glyph.width + metadata.getPadding().getHorizontal(); + glyph.u = (float)widthTiming.glyphStart / pixmap.getWidth(); + glyph.u2 = (float)widthTiming.glyphEnd / pixmap.getWidth(); + glyph.v = (float)heightTiming.glyphStart / pixmap.getHeight(); + glyph.v2 = (float)heightTiming.glyphEnd / pixmap.getHeight(); + } + } + + // Add space character + var spaceGlyph = new BitmapFont.Glyph(); + spaceGlyph.id = ' '; + spaceGlyph.width = 0; + spaceGlyph.height = 0; + spaceGlyph.xadvance = metadata.getPadding().getSpace(); + data.setGlyph(spaceGlyph.id, spaceGlyph); + + return new BitmapFont(data, region, true); + } + + /** + * Reads the vertical timing line from the pixmap. + *

+ * The timing line is a 1 pixel wide line on the left side of the pixmap that indicate the positions of the + * horizontal timing lines, and the heights of the glyphs. + *

+ * A white pixel on the timing line indicates the line is unused, a black indicates either a timing line, or the + * height of a glyph. + *

+ * It is expected that the line ends with a white pixel. + * + * @param pixmap The pixmap containing the timing line. + * @param glyphCountY The number of glyphs in the Y direction. + * @return An array of glyph heights. + */ + private static VerticalTiming[] readGlyphHeights(Pixmap pixmap, int glyphCountY) { + log.info("Reading vertical timings for {} glyphs", glyphCountY); + + enum State { + SEARCHING_FOR_LINE, + WAITING_FOR_GLYPH_HEIGHTS, + READING_GLYPH_HEIGHTS + } + State state = State.SEARCHING_FOR_LINE; + + VerticalTiming[] glyphHeights = new VerticalTiming[glyphCountY]; + VerticalTiming currentTiming = null; + int currentGlyphLine = 0; + int y = 0; + while (y < pixmap.getHeight()) { + boolean isBlack = pixmap.getPixel(0, y) == 0x000000ff; + switch (state) { + case SEARCHING_FOR_LINE -> { + if (isBlack) { + currentTiming = new VerticalTiming(); + glyphHeights[currentGlyphLine++] = currentTiming; + currentTiming.timingLine = y; + state = State.WAITING_FOR_GLYPH_HEIGHTS; + } + } + case WAITING_FOR_GLYPH_HEIGHTS -> { + if (isBlack) { + currentTiming.glyphStart = y; + state = State.READING_GLYPH_HEIGHTS; + } + } + case READING_GLYPH_HEIGHTS -> { + if (!isBlack) { + currentTiming.glyphEnd = y; + state = State.SEARCHING_FOR_LINE; + log.info("Found glyph height: {} (from {} to {})", currentTiming.getGlyphHeight(), currentTiming.glyphStart, currentTiming.glyphEnd); + } + } + } + y++; + } + + return glyphHeights; + } + + private static class VerticalTiming { + /// The Y position of the horizontal timing line. + int timingLine; + /// The start Y position of the glyph line. + int glyphStart; + /// The end Y position of the glyph line. + int glyphEnd; + + public int getGlyphHeight() { + return glyphEnd - glyphStart; + } + } + + /** + * Reads the horizontal timing lines from the pixmap. + *

+ * The timing lines are 1 pixel high lines and can occur at any Y position in the pixmap. + * The Y position is indicated through the vertical timing. + *

+ * A white pixel on the timing line indicates the line is unused, a line of black pixels indicates the width of a glyph. + * @param pixmap The pixmap containing the timing lines. + * @param glyphCountX The number of glyphs in the X direction. + * @param timingLineY The Y position of the timing line to read. + * @return An array of glyph widths. + */ + private static HorizontalTiming[] readHorizontalTimings(Pixmap pixmap, int glyphCountX, int timingLineY) { + log.info("Reading horizontal timings for {} glyphs at y={}", glyphCountX, timingLineY); + + enum State { + WAITING_FOR_GLYPH_WIDTHS, + READING_GLYPH_WIDTHS + } + State state = State.WAITING_FOR_GLYPH_WIDTHS; + + HorizontalTiming[] glyphWidths = new HorizontalTiming[glyphCountX]; + HorizontalTiming currentTiming = null; + int currentGlyphIndex = 0; + int x = 1; + while (x < pixmap.getWidth()) { + boolean isBlack = pixmap.getPixel(x, timingLineY) == 0x000000ff; + switch (state) { + case WAITING_FOR_GLYPH_WIDTHS -> { + if (isBlack) { + currentTiming = new HorizontalTiming(); + glyphWidths[currentGlyphIndex++] = currentTiming; + currentTiming.glyphStart = x; + state = State.READING_GLYPH_WIDTHS; + } + } + case READING_GLYPH_WIDTHS -> { + if (!isBlack) { + currentTiming.glyphEnd = x; + state = State.WAITING_FOR_GLYPH_WIDTHS; + log.info("Found glyph width: {} (from {} to {})", currentTiming.getGlyphWidth(), currentTiming.glyphStart, currentTiming.glyphEnd); + } + } + } + x++; + } + + return glyphWidths; + } + + private static class HorizontalTiming { + /// The X position of the vertical timing line. + int glyphStart; + /// The end X position of the vertical timing line. + int glyphEnd; + + public int getGlyphWidth() { + return glyphEnd - glyphStart; + } + } +} diff --git a/src/main/java/be/seeseemelk/diceos/system/font/FontMetadata.java b/src/main/java/be/seeseemelk/diceos/system/font/FontMetadata.java new file mode 100644 index 0000000..abbc12c --- /dev/null +++ b/src/main/java/be/seeseemelk/diceos/system/font/FontMetadata.java @@ -0,0 +1,11 @@ +package be.seeseemelk.diceos.system.font; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class FontMetadata { + private GeneralSection general; + private PaddingSection padding; +} diff --git a/src/main/java/be/seeseemelk/diceos/system/font/GeneralSection.java b/src/main/java/be/seeseemelk/diceos/system/font/GeneralSection.java new file mode 100644 index 0000000..c37c9df --- /dev/null +++ b/src/main/java/be/seeseemelk/diceos/system/font/GeneralSection.java @@ -0,0 +1,18 @@ +package be.seeseemelk.diceos.system.font; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class GeneralSection { + /** + * The image file containing the font glyphs. + */ + private String source; + + /** + * The order of the glyphs in the image file. + */ + private String order; +} diff --git a/src/main/java/be/seeseemelk/diceos/system/font/PaddingSection.java b/src/main/java/be/seeseemelk/diceos/system/font/PaddingSection.java new file mode 100644 index 0000000..7c0accd --- /dev/null +++ b/src/main/java/be/seeseemelk/diceos/system/font/PaddingSection.java @@ -0,0 +1,18 @@ +package be.seeseemelk.diceos.system.font; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class PaddingSection { + /** + * The number of pixels to pad between characters. + */ + private int horizontal; + + /** + * The width of the space character. + */ + private int space; +} diff --git a/src/main/java/be/seeseemelk/diceos/system/gfx/GraphicsContext.java b/src/main/java/be/seeseemelk/diceos/system/gfx/GraphicsContext.java index e117966..6e3a77d 100644 --- a/src/main/java/be/seeseemelk/diceos/system/gfx/GraphicsContext.java +++ b/src/main/java/be/seeseemelk/diceos/system/gfx/GraphicsContext.java @@ -1,6 +1,8 @@ package be.seeseemelk.diceos.system.gfx; +import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.NinePatch; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -13,4 +15,26 @@ public class GraphicsContext { private final Batch batch; private final int height; private final int width; + + public void draw(NinePatch image, int x, int y, int width, int height) { + image.draw(batch, x, getHeight() - height - y, width, height); + } + + public void draw(Texture texture, int x, int y) { + draw(texture, x, y, Parameters.DEFAULT); + } + + public void draw(Texture texture, int x, int y, Parameters params) { + batch.draw(texture, + x, getHeight() - y - texture.getHeight(), texture.getWidth(), texture.getHeight(), + 0, 0, texture.getWidth(), texture.getHeight(), + params.flipX, params.flipY); + } + + public static class Parameters { + public final static Parameters DEFAULT = new Parameters(); + + public boolean flipX = false; + public boolean flipY = false; + } } diff --git a/src/main/java/be/seeseemelk/diceos/system/gfx/NinePatchLoader.java b/src/main/java/be/seeseemelk/diceos/system/gfx/NinePatchLoader.java new file mode 100644 index 0000000..bffc891 --- /dev/null +++ b/src/main/java/be/seeseemelk/diceos/system/gfx/NinePatchLoader.java @@ -0,0 +1,62 @@ +package be.seeseemelk.diceos.system.gfx; + +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.NinePatch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import lombok.Getter; + +/** + * Loads a 9-patch image from a pixmap. + */ +@Getter +public class NinePatchLoader { + private final Pixmap pixmap; + private int xStart = 0; + private int xEnd = 0; + private int yStart = 0; + private int yEnd = 0; + private NinePatch ninePatch; + + public NinePatchLoader(Pixmap pixmap) { + this.pixmap = pixmap; + parse(); + } + + private void parse() { + // Top border + for (int x = 1; x < pixmap.getWidth() - 1; x++) { + int pixel = pixmap.getPixel(x, 0); + var isBlack = pixel == 0x000000ff; + if (isBlack) { + if (xStart == 0) { + xStart = x - 1; + } + xEnd = x - 1; + } + } + + // Left border + for (int y = 1; y < pixmap.getHeight() - 1; y++) { + int pixel = pixmap.getPixel(0, y); + var isBlack = pixel == 0x000000ff; + if (isBlack) { + if (yStart == 0) { + yStart = y - 1; + } + yEnd = y - 1; + } + } + + var texture = new Texture(pixmap); + var region = new TextureRegion(texture, + //1, 1, + 1, 1, + pixmap.getWidth() - 1, pixmap.getHeight() - 1 + ); + ninePatch = new NinePatch(region, + xStart, pixmap.getWidth() - xEnd - 2, + yStart, pixmap.getHeight() - yEnd - 2 + ); + } +} diff --git a/src/main/resources/system/font.fnt b/src/main/resources/system/font.fnt deleted file mode 100644 index 5468f9d..0000000 --- a/src/main/resources/system/font.fnt +++ /dev/null @@ -1,4 +0,0 @@ -info face="Dice Font" size=8 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=0 aa=0 padding=1,1,1,1 spacing=1,1 -common lineHeight=8 base=7 scaleW=160 scaleH=28 pages=1 packed=0 alphaChnl=0 -page id=0 file="font.png" -char id=65 x=5 y=0 width=60 height=7 xoffset=5 yoffset=5 xadvance=6 page=0 chnl=15 diff --git a/src/main/resources/system/font.toml b/src/main/resources/system/font.toml new file mode 100644 index 0000000..8ba2b1b --- /dev/null +++ b/src/main/resources/system/font.toml @@ -0,0 +1,10 @@ +[general] +source = "system/font.png" +order = """ +aAbBcCdDeEfFgGhHiIjJkKlLmMnN +oOpPqQrRsStTuUvVwWxXyYzZ +""" + +[padding] +horizontal = 1 +space = 2 diff --git a/src/main/resources/system/font.txt b/src/main/resources/system/font.txt deleted file mode 100644 index d1daced..0000000 --- a/src/main/resources/system/font.txt +++ /dev/null @@ -1,2 +0,0 @@ -aAbBcCdDeEfFgGhHiIjJkKlLmMnN -oOpPqQrRsStTuUvVwWxXyYzZ diff --git a/src/main/resources/system/menubar.png b/src/main/resources/system/menubar.png index 9d6e300..87464aa 100644 Binary files a/src/main/resources/system/menubar.png and b/src/main/resources/system/menubar.png differ