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 "" msgid ""
msgstr "" msgstr ""
"Language: en_US\n" "Language: en_US"
"MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8"
"Content-Type: text/plain; charset=UTF-8\n" "Plural-Forms: nplurals=2; plural=(n != 1);"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "ui.test" msgid "ui.test"
msgstr "Hello this is a test." msgstr "Hello this is a test."
@@ -14,3 +12,4 @@ msgstr "This is a map test."
msgid "test.test2" msgid "test.test2"
msgstr "This is another test." msgstr "This is another test."

View File

@@ -8,9 +8,9 @@
#pragma once #pragma once
#include "locale/language/keys.h" #include "locale/language/keys.h"
#include "error/error.h" #include "error/error.h"
#include "duskdefs.h"
#include <zip.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 #define ASSET_LANG_CHUNK_CACHE 4 // Number of chunks to cache in memory
#pragma pack(push, 1) #pragma pack(push, 1)

View File

@@ -6,6 +6,9 @@
CHUNK_WIDTH = 16 CHUNK_WIDTH = 16
CHUNK_HEIGHT = 16 CHUNK_HEIGHT = 16
CHUNK_DEPTH = 4 CHUNK_DEPTH = 4
TILE_WIDTH = 16.0 TILE_WIDTH = 16.0
TILE_HEIGHT = 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.args import args
from assetstool.assetcache import assetCache, assetGetCache from assetstool.assetcache import assetCache, assetGetCache
from assetstool.assethelpers import getAssetRelativePath from assetstool.assethelpers import getAssetRelativePath
from dusk.defs import defs
import polib import polib
import re 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_DATA = {}
LANGUAGE_KEYS = [] 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...