diff --git a/assets/locale/en_US.po b/assets/locale/en_US.po index 1399498..d182dd0 100644 --- a/assets/locale/en_US.po +++ b/assets/locale/en_US.po @@ -1,10 +1,8 @@ msgid "" msgstr "" -"Language: en_US\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language: en_US" +"Content-Type: text/plain; charset=UTF-8" +"Plural-Forms: nplurals=2; plural=(n != 1);" msgid "ui.test" msgstr "Hello this is a test." @@ -13,4 +11,5 @@ msgid "map.test" msgstr "This is a map test." msgid "test.test2" -msgstr "This is another test." \ No newline at end of file +msgstr "This is another test." + diff --git a/src/asset/type/assetlanguage.h b/src/asset/type/assetlanguage.h index 213148a..676a6b3 100644 --- a/src/asset/type/assetlanguage.h +++ b/src/asset/type/assetlanguage.h @@ -8,9 +8,9 @@ #pragma once #include "locale/language/keys.h" #include "error/error.h" +#include "duskdefs.h" #include -#define ASSET_LANG_CHUNK_CHAR_COUNT 6 * 1024 // 6 KB per chunk #define ASSET_LANG_CHUNK_CACHE 4 // Number of chunks to cache in memory #pragma pack(push, 1) diff --git a/src/duskdefs.env b/src/duskdefs.env index c94bef6..bdd1add 100644 --- a/src/duskdefs.env +++ b/src/duskdefs.env @@ -6,6 +6,9 @@ CHUNK_WIDTH = 16 CHUNK_HEIGHT = 16 CHUNK_DEPTH = 4 + TILE_WIDTH = 16.0 TILE_HEIGHT = 16.0 -TILE_DEPTH = 11.36 \ No newline at end of file +TILE_DEPTH = 16.0 + +ASSET_LANG_CHUNK_CHAR_COUNT = 6144 \ No newline at end of file diff --git a/tools/assetstool/processlanguage.py b/tools/assetstool/processlanguage.py index 2cb40d0..30673bb 100644 --- a/tools/assetstool/processlanguage.py +++ b/tools/assetstool/processlanguage.py @@ -3,10 +3,11 @@ import os from assetstool.args import args from assetstool.assetcache import assetCache, assetGetCache from assetstool.assethelpers import getAssetRelativePath +from dusk.defs import defs import polib import re -LANGUAGE_CHUNK_CHAR_COUNT = 6 * 1024 # 6 KB per chunk +LANGUAGE_CHUNK_CHAR_COUNT = int(defs.get('ASSET_LANG_CHUNK_CHAR_COUNT')) LANGUAGE_DATA = {} LANGUAGE_KEYS = [] diff --git a/tools/editor.py b/tools/editor.py new file mode 100755 index 0000000..d597dd4 --- /dev/null +++ b/tools/editor.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +import sys +from PyQt5.QtWidgets import ( + QApplication, QVBoxLayout, QPushButton, + QDialog +) +from OpenGL.GL import * +from OpenGL.GLU import * +from editortool.maptool import MapWindow +from editortool.langtool import LangToolWindow + +DEFAULT_TOOL = None # Set to "map" or "language" to auto-select for testing + +TOOLS = [ + ("Map Editor", "map", MapWindow), + ("Language Editor", "language", LangToolWindow) +] + +class EditorChoiceDialog(QDialog): + def __init__(self): + super().__init__() + self.setWindowTitle("Choose Tool") + layout = QVBoxLayout(self) + self.selected = None + for label, key, win_cls in TOOLS: + btn = QPushButton(label) + btn.clicked.connect(lambda checked, w=win_cls: self.choose_tool(w)) + layout.addWidget(btn) + + def choose_tool(self, win_cls): + self.selected = win_cls + self.accept() + + def get_choice(self): + return self.selected + +def main(): + app = QApplication(sys.argv) + tool_map = { key: win_cls for _, key, win_cls in TOOLS } + if DEFAULT_TOOL in tool_map: + win_cls = tool_map[DEFAULT_TOOL] + else: + choice_dialog = EditorChoiceDialog() + if choice_dialog.exec_() == QDialog.Accepted: + win_cls = choice_dialog.get_choice() + else: + sys.exit(0) + win = win_cls() + win.show() + sys.exit(app.exec_()) + +if __name__ == "__main__": + main() diff --git a/tools/editortool/langtool.py b/tools/editortool/langtool.py new file mode 100644 index 0000000..c7a23bc --- /dev/null +++ b/tools/editortool/langtool.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +from PyQt5.QtWidgets import QMainWindow, QApplication, QAction, QMenuBar, QMessageBox, QFileDialog, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QTableWidget, QTableWidgetItem, QHeaderView, QPushButton +from PyQt5.QtCore import Qt +import sys +import os + +class LangToolWindow(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("Dusk Language Editor") + self.showMaximized() + self.setGeometry(100, 100, 800, 600) + self.current_file = None + self.dirty = False + self.init_menu() + self.init_ui() + + def init_ui(self): + central = QWidget() + layout = QVBoxLayout(central) + + # Header editor + header_layout = QHBoxLayout() + header_layout.addWidget(QLabel("Language:")) + self.language_edit = QLineEdit() + header_layout.addWidget(self.language_edit) + header_layout.addWidget(QLabel("Plural-Forms:")) + self.plural_edit = QLineEdit() + header_layout.addWidget(self.plural_edit) + header_layout.addWidget(QLabel("Content-Type:")) + self.content_type_edit = QLineEdit("text/plain; charset=UTF-8") + header_layout.addWidget(self.content_type_edit) + layout.addLayout(header_layout) + + # PO entries editor + self.po_table = QTableWidget() + self.po_table.setColumnCount(2) + self.po_table.setHorizontalHeaderLabels(["msgid", "msgstr"]) + self.po_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + self.po_table.verticalHeader().setMinimumWidth(22) + self.po_table.verticalHeader().setDefaultAlignment(Qt.AlignRight | Qt.AlignVCenter) + layout.addWidget(self.po_table) + + # Row add/remove buttons + row_btn_layout = QHBoxLayout() + add_row_btn = QPushButton("Add Row") + remove_row_btn = QPushButton("Remove Row") + row_btn_layout.addWidget(add_row_btn) + row_btn_layout.addWidget(remove_row_btn) + layout.addLayout(row_btn_layout) + + add_row_btn.clicked.connect(self.add_row) + remove_row_btn.clicked.connect(self.remove_row) + self.add_row_btn = add_row_btn + self.remove_row_btn = remove_row_btn + + self.setCentralWidget(central) + + # Connect edits to dirty flag + self.language_edit.textChanged.connect(self.set_dirty) + self.plural_edit.textChanged.connect(self.set_dirty) + self.content_type_edit.textChanged.connect(self.set_dirty) + self.po_table.itemChanged.connect(self.set_dirty) + + def set_dirty(self): + self.dirty = True + self.update_save_action() + + def init_menu(self): + menubar = self.menuBar() + file_menu = menubar.addMenu("File") + + new_action = QAction("New", self) + open_action = QAction("Open", self) + save_action = QAction("Save", self) + save_as_action = QAction("Save As", self) + + new_action.triggered.connect(lambda: self.handle_file_action("new")) + open_action.triggered.connect(lambda: self.handle_file_action("open")) + save_action.triggered.connect(self.handle_save) + save_as_action.triggered.connect(self.handle_save_as) + + file_menu.addAction(new_action) + file_menu.addAction(open_action) + file_menu.addAction(save_action) + file_menu.addAction(save_as_action) + + self.save_action = save_action # Store reference for enabling/disabling + self.update_save_action() + + def update_save_action(self): + self.save_action.setEnabled(self.current_file is not None) + + def handle_file_action(self, action): + if self.dirty: + reply = QMessageBox.question(self, "Save Changes?", "Do you want to save changes before proceeding?", QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) + if reply == QMessageBox.Cancel: + return + elif reply == QMessageBox.Yes: + self.handle_save() + if action == "new": + self.new_file() + elif action == "open": + default_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../assets/locale")) + file_path, _ = QFileDialog.getOpenFileName(self, "Open Language File", default_dir, "PO Files (*.po)") + if file_path: + self.open_file(file_path) + + def new_file(self): + self.current_file = None + self.dirty = False + self.language_edit.setText("") + self.plural_edit.setText("") + self.po_table.setRowCount(0) + self.update_save_action() + + def open_file(self, file_path): + self.current_file = file_path + self.dirty = False + self.update_save_action() + self.load_po_file(file_path) + + def load_po_file(self, file_path): + language = "" + plural = "" + entries = [] + with open(file_path, "r", encoding="utf-8") as f: + lines = f.readlines() + i = 0 + while i < len(lines): + line = lines[i].strip() + if line.startswith('"Language:'): + language = line.split(':',1)[1].strip('"\n ') + elif line.startswith('"Plural-Forms:'): + plural = line.split(':',1)[1].strip('"\n ') + elif line.startswith('msgid'): + msgid = line[6:].strip('"') + i += 1 + msgstr = lines[i].strip()[7:].strip('"') if i < len(lines) and lines[i].strip().startswith('msgstr') else "" + entries.append((msgid, msgstr)) + i += 1 + self.language_edit.setText(language) + self.plural_edit.setText(plural) + self.po_table.setRowCount(len(entries)) + for row, (msgid, msgstr) in enumerate(entries): + self.po_table.setItem(row, 0, QTableWidgetItem(msgid)) + self.po_table.setItem(row, 1, QTableWidgetItem(msgstr)) + + def save_file(self, file_path): + language = self.language_edit.text() + plural = self.plural_edit.text() + content_type = self.content_type_edit.text() + entries = [] + for row in range(self.po_table.rowCount()): + msgid = self.po_table.item(row, 0) + msgstr = self.po_table.item(row, 1) + msgid_text = msgid.text() if msgid else "" + msgstr_text = msgstr.text() if msgstr else "" + if msgid_text or msgstr_text: + entries.append((msgid_text, msgstr_text)) + with open(file_path, "w", encoding="utf-8") as f: + f.write('msgid ""\nmsgstr ""\n') + f.write(f'"Language: {language}"\n') + f.write(f'"Content-Type: {content_type}"\n') + f.write(f'"Plural-Forms: {plural}"\n') + f.write('\n') + for msgid, msgstr in entries: + f.write(f'msgid "{msgid}"\nmsgstr "{msgstr}"\n\n') + self.dirty = False + self.update_save_action() + + def handle_save(self): + if self.current_file: + self.save_file(self.current_file) + else: + self.handle_save_as() + + def handle_save_as(self): + default_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../assets/locale")) + file_path, _ = QFileDialog.getSaveFileName(self, "Save Language File As", default_dir, "PO Files (*.po)") + if file_path: + self.current_file = file_path + self.update_save_action() + self.save_file(file_path) + + def add_row(self): + row = self.po_table.rowCount() + self.po_table.insertRow(row) + self.po_table.setItem(row, 0, QTableWidgetItem("")) + self.po_table.setItem(row, 1, QTableWidgetItem("")) + self.po_table.setCurrentCell(row, 0) + self.set_dirty() + + def remove_row(self): + row = self.po_table.currentRow() + if row >= 0: + self.po_table.removeRow(row) + self.set_dirty() + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = LangToolWindow() + window.show() + sys.exit(app.exec_()) diff --git a/tools/editortool/maptool.py b/tools/editortool/maptool.py new file mode 100644 index 0000000..e7cdc5c --- /dev/null +++ b/tools/editortool/maptool.py @@ -0,0 +1,225 @@ +import sys +import os +from PyQt5.QtCore import Qt, QTimer +from PyQt5.QtWidgets import ( + QApplication, QMainWindow, QWidget, + QHBoxLayout, QVBoxLayout, QPushButton, + QLabel, QSlider, QOpenGLWidget, QDialog, QRadioButton, QDialogButtonBox, + QAction, QFileDialog, QLineEdit +) +from OpenGL.GL import * +from OpenGL.GLU import * + +class GLWidget(QOpenGLWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.xRot = 20.0 + self.yRot = 30.0 + self.zRot = 0.0 + + self.rotation_speed = 2.0 + + self.timer = QTimer(self) + self.timer.timeout.connect(self.updateRotation) + + # ---------- OpenGL lifecycle ---------- + def initializeGL(self): + # Check that context is valid + version = glGetString(GL_VERSION) + print("GL version:", version) + + glClearColor(0.1, 0.1, 0.1, 1.0) + glEnable(GL_DEPTH_TEST) + glShadeModel(GL_SMOOTH) + + glEnable(GL_COLOR_MATERIAL) + glEnable(GL_LIGHTING) + glEnable(GL_LIGHT0) + + light_position = [4.0, 4.0, 8.0, 1.0] + glLightfv(GL_LIGHT0, GL_POSITION, light_position) + + def resizeGL(self, w, h): + if h == 0: + h = 1 + + glViewport(0, 0, w, h) + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + glLoadIdentity() + gluPerspective(45.0, float(w) / float(h), 0.1, 100.0) + glMatrixMode(GL_MODELVIEW) + + def paintGL(self): + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + + glTranslatef(0.0, 0.0, -7.0) + + glRotatef(self.xRot, 1.0, 0.0, 0.0) + glRotatef(self.yRot, 0.0, 1.0, 0.0) + glRotatef(self.zRot, 0.0, 0.0, 1.0) + + self.drawCube() + + # ---------- 3D object ---------- + + def drawCube(self): + glBegin(GL_QUADS) + + # Front (red) + glColor3f(1.0, 0.0, 0.0) + glVertex3f(-1.0, -1.0, 1.0) + glVertex3f( 1.0, -1.0, 1.0) + glVertex3f( 1.0, 1.0, 1.0) + glVertex3f(-1.0, 1.0, 1.0) + + # Back (green) + glColor3f(0.0, 1.0, 0.0) + glVertex3f(-1.0, -1.0, -1.0) + glVertex3f(-1.0, 1.0, -1.0) + glVertex3f( 1.0, 1.0, -1.0) + glVertex3f( 1.0, -1.0, -1.0) + + # Top (blue) + glColor3f(0.0, 0.0, 1.0) + glVertex3f(-1.0, 1.0, -1.0) + glVertex3f(-1.0, 1.0, 1.0) + glVertex3f( 1.0, 1.0, 1.0) + glVertex3f( 1.0, 1.0, -1.0) + + # Bottom (yellow) + glColor3f(1.0, 1.0, 0.0) + glVertex3f(-1.0, -1.0, -1.0) + glVertex3f( 1.0, -1.0, -1.0) + glVertex3f( 1.0, -1.0, 1.0) + glVertex3f(-1.0, -1.0, 1.0) + + # Right (magenta) + glColor3f(1.0, 0.0, 1.0) + glVertex3f( 1.0, -1.0, -1.0) + glVertex3f( 1.0, 1.0, -1.0) + glVertex3f( 1.0, 1.0, 1.0) + glVertex3f( 1.0, -1.0, 1.0) + + # Left (cyan) + glColor3f(0.0, 1.0, 1.0) + glVertex3f(-1.0, -1.0, -1.0) + glVertex3f(-1.0, -1.0, 1.0) + glVertex3f(-1.0, 1.0, 1.0) + glVertex3f(-1.0, 1.0, -1.0) + + glEnd() + + # ---------- Animation control ---------- + + def startRotation(self): + if not self.timer.isActive(): + self.timer.start(30) # ms + + def stopRotation(self): + if self.timer.isActive(): + self.timer.stop() + + def resetView(self): + self.xRot = 20.0 + self.yRot = 30.0 + self.zRot = 0.0 + self.update() + + def updateRotation(self): + self.xRot += self.rotation_speed + self.yRot += self.rotation_speed * 0.8 + self.zRot += self.rotation_speed * 0.5 + self.update() + + +class MapWindow(QMainWindow): + def __init__(self): + super().__init__() + + self.setWindowTitle("3D Cube with GUI Buttons (QOpenGLWidget)") + self.resize(1280, 720) + + # File menu setup + menubar = self.menuBar() + file_menu = menubar.addMenu("File") + + action_new = QAction("New", self) + action_open = QAction("Open", self) + action_save = QAction("Save", self) + action_save_as = QAction("Save As", self) + + file_menu.addAction(action_new) + file_menu.addAction(action_open) + file_menu.addAction(action_save) + file_menu.addAction(action_save_as) + + action_new.triggered.connect(self.newFile) + action_open.triggered.connect(self.openFile) + action_save.triggered.connect(self.saveFile) + action_save_as.triggered.connect(self.saveFileAs) + + self.current_file = None + + central = QWidget() + self.setCentralWidget(central) + + main_layout = QHBoxLayout(central) + + # Left panel + left_panel = QVBoxLayout() + chunk_info_label = QLabel("CHUNK INFO") + left_panel.addWidget(chunk_info_label) + left_panel.addStretch() + + # Right panel + right_panel = QVBoxLayout() + map_info_label = QLabel("Map Information") + right_panel.addWidget(map_info_label) + map_title_label = QLabel("Map Title") + map_title_input = QLineEdit() + right_panel.addWidget(map_title_label) + right_panel.addWidget(map_title_input) + right_panel.addStretch() + + # Add panels to main layout + left_widget = QWidget() + left_widget.setLayout(left_panel) + left_widget.setFixedWidth(200) + main_layout.addWidget(left_widget) + + # Center panel (GLWidget + controls) + self.gl_widget = GLWidget(self) + main_layout.addWidget(self.gl_widget, stretch=3) + + right_widget = QWidget() + right_widget.setLayout(right_panel) + right_widget.setFixedWidth(250) + main_layout.addWidget(right_widget) + + def newFile(self): + # Implement new file logic here + self.current_file = None + # ...clear relevant data... + + def openFile(self): + default_dir = os.path.join(os.path.dirname(__file__), '../../assets/map/') + fname, _ = QFileDialog.getOpenFileName(self, "Open JSON File", default_dir, "JSON Files (*.json)") + if fname: + self.current_file = fname + # ...load file logic... + + def saveFile(self): + if self.current_file: + # ...save logic... + pass + else: + self.saveFileAs() + + def saveFileAs(self): + default_dir = os.path.join(os.path.dirname(__file__), '../../assets/map/') + fname, _ = QFileDialog.getSaveFileName(self, "Save JSON File As", default_dir, "JSON Files (*.json)") + if fname: + self.current_file = fname + # ...save logic...