Start work on editor
All checks were successful
Build Dusk / build-linux (push) Successful in 54s
Build Dusk / build-psp (push) Successful in 1m10s

This commit is contained in:
2025-11-15 23:38:31 -06:00
parent 8525138594
commit 68b63d3007
7 changed files with 494 additions and 9 deletions

View File

@@ -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."
msgstr "This is another test."

View File

@@ -8,9 +8,9 @@
#pragma once
#include "locale/language/keys.h"
#include "error/error.h"
#include "duskdefs.h"
#include <zip.h>
#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)

View File

@@ -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
TILE_DEPTH = 16.0
ASSET_LANG_CHUNK_CHAR_COUNT = 6144

View File

@@ -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 = []

53
tools/editor.py Executable file
View File

@@ -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()

View File

@@ -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_())

225
tools/editortool/maptool.py Normal file
View File

@@ -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...