diff --git a/data/map project.tiled-session b/data/map project.tiled-session index 18bcdef..5672977 100644 --- a/data/map project.tiled-session +++ b/data/map project.tiled-session @@ -1,8 +1,8 @@ { - "activeFile": "map.tmj", + "activeFile": "minogram.tsx", "expandedProjectPaths": [ - ".", - "templates" + "templates", + "." ], "fileStates": { ":/automap-tiles.tsx": { @@ -20,6 +20,10 @@ "y": 6836.833333333333 } }, + "minogram.tsx": { + "scaleInDock": 1, + "scaleInEditor": 16 + }, "overworld.tsx": { "scaleInDock": 1, "scaleInEditor": 4 @@ -30,19 +34,23 @@ "last.objectTemplatePath": "/home/yourwishes/htdocs/dusk/data/templates", "openFiles": [ "map.tmj", - "overworld.tsx" + "overworld.tsx", + "minogram.tsx" ], "project": "map project.tiled-project", "property.type": "int", "recentFiles": [ - "overworld.tsx", "map.tmj", + "overworld.tsx", + "minogram.tsx", "entities.tsx" ], "tileset.lastUsedFilter": "Tiled tileset files (*.tsx *.xml)", "tileset.lastUsedFormat": "tsx", + "tileset.margin": 1, + "tileset.spacing": 1, "tileset.tileSize": { - "height": 16, - "width": 16 + "height": 9, + "width": 5 } } diff --git a/data/minogram.tsx b/data/minogram.tsx new file mode 100644 index 0000000..373dc3f --- /dev/null +++ b/data/minogram.tsx @@ -0,0 +1,4 @@ + + + + diff --git a/data/minogram_6x10.png b/data/minogram_6x10.png new file mode 100644 index 0000000..a198a75 Binary files /dev/null and b/data/minogram_6x10.png differ diff --git a/src/dusk/ui/font.h b/src/dusk/ui/font.h index 3b577a9..f7b7d1e 100644 --- a/src/dusk/ui/font.h +++ b/src/dusk/ui/font.h @@ -6,7 +6,7 @@ */ #pragma once -#include "dusk.h" +#include "ui/fontdata.h" #define FONT_HEIGHT 8 #define FONT_WIDTH FONT_HEIGHT diff --git a/src/dusk/ui/uitextbox.h b/src/dusk/ui/uitextbox.h index 094fc37..1a2563d 100644 --- a/src/dusk/ui/uitextbox.h +++ b/src/dusk/ui/uitextbox.h @@ -15,10 +15,10 @@ FONT_LINE_HEIGHT * UI_TEXTBOX_LINES_PER_PAGE \ ) -#define UI_TEXTBOX_BORDER_WIDTH 1 -#define UI_TEXTBOX_BORDER_HEIGHT 1 -#define UI_TEXTBOX_PADDING_X 1 -#define UI_TEXTBOX_PADDING_Y 1 +#define UI_TEXTBOX_BORDER_WIDTH 4 +#define UI_TEXTBOX_BORDER_HEIGHT 4 +#define UI_TEXTBOX_PADDING_X 4 +#define UI_TEXTBOX_PADDING_Y 4 #define UI_TEXTBOX_WIDTH_INNER ( \ UI_TEXTBOX_WIDTH - (UI_TEXTBOX_BORDER_WIDTH * 2) - \ (UI_TEXTBOX_PADDING_X * 2) \ diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 2698815..063df48 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -5,4 +5,5 @@ # Tools add_subdirectory(mapcompile) -add_subdirectory(tilecompile) \ No newline at end of file +add_subdirectory(tilecompile) +add_subdirectory(fontcompile) \ No newline at end of file diff --git a/tools/fontcompile/CMakeLists.txt b/tools/fontcompile/CMakeLists.txt new file mode 100644 index 0000000..3ef1d72 --- /dev/null +++ b/tools/fontcompile/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +find_package(Python3 COMPONENTS Interpreter REQUIRED) + +# Custom command to generate all header files +add_custom_target(DUSK_FONT + COMMAND + ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/fontcompile.py + --output ${DUSK_GENERATED_HEADERS_DIR}/ui/fontdata.h + --input ${DUSK_DATA_DIR}/minogram.tsx + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/fontcompile.py + COMMENT "Generating font header file" + VERBATIM +) + +# Ensure headers are generated before compiling main +add_dependencies(${DUSK_TARGET_NAME} DUSK_FONT) \ No newline at end of file diff --git a/tools/fontcompile/fontcompile.py b/tools/fontcompile/fontcompile.py new file mode 100644 index 0000000..482e3b7 --- /dev/null +++ b/tools/fontcompile/fontcompile.py @@ -0,0 +1,142 @@ +import sys, os +import argparse +from datetime import datetime +import xml.etree.ElementTree as ET +from PIL import Image + +# Input font information. +CHARACTER_MAP = [ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '-', '=', + '(', ')', '[', ']', '{', '}', '<', '>', '/', '*', ':', '#', '%', + '!', '?', '.', ',', "'", '"', '@', '&', '$', ' ' +] +CHAR_START = 0x20 # ASCII space character +CHAR_END = 0x7E # ASCII tilde character (exclusive) +CHARS_TOTAL = CHAR_END - CHAR_START + +# Check if the script is run with the correct arguments +parser = argparse.ArgumentParser(description="Generate chunk header files") +parser.add_argument('--output', required=True, help='File to write header') +parser.add_argument('--input', required=True, help='Input XML from tiled') +args = parser.parse_args() + +# Ensure outdir exists +outputFile = args.output +outputDir = os.path.dirname(outputFile) +os.makedirs(outputDir, exist_ok=True) + +# Read the XML file +inputFile = args.input +if not os.path.exists(inputFile): + print(f"Error: Input file '{inputFile}' does not exist.") + sys.exit(1) + +# Find root element +tree = ET.parse(inputFile) +root = tree.getroot() +# Check if the root element is 'tileset' +if root.tag != 'tileset': + print(f"Error: Expected root element 'tileset', found '{root.tag}'") + sys.exit(1) + +# Shoul have tilewidth and tileheight attributes +if 'tilewidth' not in root.attrib or 'tileheight' not in root.attrib: + print("Error: 'tileset' element must have 'tilewidth' and 'tileheight' attributes") + sys.exit(1) + +if 'tilecount' not in root.attrib: + print("Error: 'tileset' element must have 'tilecount' attribute") + sys.exit(1) + +# Find image element +image = root.find('image') +if image is None: + print("Error: 'tileset' element must contain an 'image' element") + sys.exit(1) + +# Ensure image has 'source' attribute +if 'source' not in image.attrib: + print("Error: 'image' element must have a 'source' attribute") + sys.exit(1) + +# Ensure image source exists +inputDir = os.path.dirname(inputFile) + +imageSource = os.path.join(inputDir, image.attrib['source']) +if not os.path.exists(imageSource): + print(f"Error: Image source '{imageSource}' does not exist.") + sys.exit(1) + +# Ensure image has 'width' and 'height' attributes +if 'width' not in image.attrib or 'height' not in image.attrib: + print("Error: 'image' element must have 'width' and 'height' attributes") + sys.exit(1) + +# Ensure image is readable +try: + img = Image.open(imageSource) +except Exception as e: + print(f"Error: Unable to open image '{imageSource}': {e}") + sys.exit(1) + +# Ensure image dimensions match the attributes +if img.width != int(image.attrib['width']) or img.height != int(image.attrib['height']): + print(f"Error: Image dimensions ({img.width}x{img.height}) do not match attributes ({image.attrib['width']}x{image.attrib['height']})") + sys.exit(1) + +# Prepare header content +now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") +tileCount = int(root.attrib['tilecount']) + +outputTileIndexes = [] +for i in range(CHARS_TOTAL): + # For some reason the input tilemap is not in ASCII, so we are going to map + # the tiles to the ASCII characters we expect, we start at 0x20 (space) + + # Find the index of the character in the CHARACTER_MAP + inputIndex = -1 + for j, char in enumerate(CHARACTER_MAP): + if ord(char) == (CHAR_START + i): + inputIndex = j + break + + if inputIndex == -1: + print(f"Warning: Character '{chr(CHAR_START + i)}' not found in CHARACTER_MAP") + outputTileIndexes.append(0) # Use 0 for missing characters (space) + continue + + outputTileIndexes.append(inputIndex) + +with open(outputFile, 'w') as f: + f.write(f"// Generated at {now}\n") + f.write("#pragma once\n") + f.write("#include \"dusk.h\"\n\n") + f.write(f"#define FONT_TILE_WIDTH {root.attrib['tilewidth']}\n") + f.write(f"#define FONT_TILE_HEIGHT {root.attrib['tileheight']}\n") + f.write(f"#define FONT_TILE_COUNT {len(outputTileIndexes)}\n") + f.write(f"#define FONT_CHAR_START {CHAR_START}\n") + f.write(f"#define FONT_CHAR_END {CHAR_END}\n\n") + + f.write("static const uint8_t TILE_PIXEL_DATA[FONT_TILE_COUNT][FONT_TILE_WIDTH * FONT_TILE_HEIGHT] = {\n") + for i in range(len(outputTileIndexes)): + tileIndex = outputTileIndexes[i] + f.write(f" // Character {i} ('{chr(CHAR_START + i)}'). Read from {tileIndex} tile.\n") + f.write(f" {{") + + # Read the tile from the image + tileX = (tileIndex % (img.width // int(root.attrib['tilewidth']))) * int(root.attrib['tilewidth']) + tileY = (tileIndex // (img.width // int(root.attrib['tilewidth']))) * int(root.attrib['tileheight']) + tile = img.crop((tileX, tileY, tileX + int(root.attrib['tilewidth']), tileY + int(root.attrib['tileheight']))) + + # Pixel is either 0 (transparent) or 1 (opaque) + for y in range(int(root.attrib['tileheight'])): + for x in range(int(root.attrib['tilewidth'])): + pixel = tile.getpixel((x, y)) + f.write(f"0x{1 if pixel[3] > 0 else 0:02X}, ") + + f.write("},\n") + f.write("};\n\n") \ No newline at end of file