mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-24 13:44:58 +00:00
feat: Add Lua script editor with syntax highlighting and tabbed interface in GUI
This commit is contained in:
@@ -386,10 +386,11 @@ def gui(args: argparse.Namespace) -> None:
|
||||
from PyQt6.QtWidgets import (
|
||||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QPushButton, QLabel, QTextEdit, QComboBox, QListWidget, QListWidgetItem,
|
||||
QSplitter, QMenuBar, QDialog, QDialogButtonBox, QFormLayout, QMessageBox
|
||||
QSplitter, QMenuBar, QDialog, QDialogButtonBox, QFormLayout, QMessageBox,
|
||||
QPlainTextEdit, QTabWidget
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QProcess, QSize
|
||||
from PyQt6.QtGui import QFont, QPalette, QColor, QAction
|
||||
from PyQt6.QtGui import QFont, QPalette, QColor, QAction, QSyntaxHighlighter, QTextCharFormat
|
||||
except ImportError:
|
||||
raise SystemExit(
|
||||
"PyQt6 is not installed. Install it with:\n"
|
||||
@@ -397,6 +398,74 @@ def gui(args: argparse.Namespace) -> None:
|
||||
)
|
||||
|
||||
import sys
|
||||
import re
|
||||
|
||||
class LuaSyntaxHighlighter(QSyntaxHighlighter):
|
||||
"""Simple Lua syntax highlighter"""
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
# Define colors
|
||||
keyword_color = QColor("#569cd6") # Blue
|
||||
string_color = QColor("#ce9178") # Orange
|
||||
comment_color = QColor("#6a9955") # Green
|
||||
function_color = QColor("#dcdcaa") # Yellow
|
||||
number_color = QColor("#b5cea8") # Light green
|
||||
|
||||
# Define formatting
|
||||
self.keyword_format = QTextCharFormat()
|
||||
self.keyword_format.setForeground(keyword_color)
|
||||
self.keyword_format.setFontWeight(700)
|
||||
|
||||
self.string_format = QTextCharFormat()
|
||||
self.string_format.setForeground(string_color)
|
||||
|
||||
self.comment_format = QTextCharFormat()
|
||||
self.comment_format.setForeground(comment_color)
|
||||
self.comment_format.setFontItalic(True)
|
||||
|
||||
self.function_format = QTextCharFormat()
|
||||
self.function_format.setForeground(function_color)
|
||||
|
||||
self.number_format = QTextCharFormat()
|
||||
self.number_format.setForeground(number_color)
|
||||
|
||||
# Lua keywords
|
||||
keywords = [
|
||||
'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for',
|
||||
'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
|
||||
'return', 'then', 'true', 'until', 'while'
|
||||
]
|
||||
|
||||
# Build highlighting rules
|
||||
self.rules = []
|
||||
|
||||
# Keywords
|
||||
for word in keywords:
|
||||
pattern = f'\\b{word}\\b'
|
||||
self.rules.append((re.compile(pattern), self.keyword_format))
|
||||
|
||||
# Numbers
|
||||
self.rules.append((re.compile(r'\b\d+\.?\d*\b'), self.number_format))
|
||||
|
||||
# Functions
|
||||
self.rules.append((re.compile(r'\b[A-Za-z_][A-Za-z0-9_]*(?=\s*\()'), self.function_format))
|
||||
|
||||
# Strings (double quotes)
|
||||
self.rules.append((re.compile(r'"[^"\\]*(\\.[^"\\]*)*"'), self.string_format))
|
||||
|
||||
# Strings (single quotes)
|
||||
self.rules.append((re.compile(r"'[^'\\]*(\\.[^'\\]*)*'"), self.string_format))
|
||||
|
||||
# Comments
|
||||
self.rules.append((re.compile(r'--[^\n]*'), self.comment_format))
|
||||
|
||||
def highlightBlock(self, text):
|
||||
"""Apply syntax highlighting to the given block of text"""
|
||||
for pattern, fmt in self.rules:
|
||||
for match in pattern.finditer(text):
|
||||
start, end = match.span()
|
||||
self.setFormat(start, end - start, fmt)
|
||||
|
||||
class BuildSettingsDialog(QDialog):
|
||||
"""Dialog for configuring build settings"""
|
||||
@@ -655,9 +724,113 @@ def gui(args: argparse.Namespace) -> None:
|
||||
|
||||
right_layout.addWidget(detail_header)
|
||||
|
||||
# Console output
|
||||
# Tabbed interface for code editor and console
|
||||
self.tab_widget = QTabWidget()
|
||||
self.tab_widget.setStyleSheet("""
|
||||
QTabWidget::pane {
|
||||
background-color: #1b2838;
|
||||
border: none;
|
||||
}
|
||||
QTabBar::tab {
|
||||
background-color: #1b2838;
|
||||
color: #8f98a0;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
margin-right: 2px;
|
||||
}
|
||||
QTabBar::tab:selected {
|
||||
background-color: #2a475e;
|
||||
color: #c6d1db;
|
||||
}
|
||||
QTabBar::tab:hover {
|
||||
background-color: #243447;
|
||||
}
|
||||
""")
|
||||
|
||||
# Lua Code Editor Tab
|
||||
editor_container = QWidget()
|
||||
editor_layout = QVBoxLayout(editor_container)
|
||||
editor_layout.setContentsMargins(10, 10, 10, 10)
|
||||
|
||||
# Editor toolbar
|
||||
editor_toolbar = QHBoxLayout()
|
||||
self.lua_file_label = QLabel("No file loaded")
|
||||
self.lua_file_label.setStyleSheet("color: #8f98a0; font-size: 9pt;")
|
||||
editor_toolbar.addWidget(self.lua_file_label)
|
||||
|
||||
editor_toolbar.addStretch()
|
||||
|
||||
self.save_lua_btn = QPushButton("💾 Save")
|
||||
self.save_lua_btn.setEnabled(False)
|
||||
self.save_lua_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #2a475e;
|
||||
color: #c6d1db;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
padding: 5px 15px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #3e5c78;
|
||||
}
|
||||
QPushButton:disabled {
|
||||
background-color: #1b2838;
|
||||
color: #4e5a66;
|
||||
}
|
||||
""")
|
||||
self.save_lua_btn.clicked.connect(self.save_lua_script)
|
||||
editor_toolbar.addWidget(self.save_lua_btn)
|
||||
|
||||
self.reload_lua_btn = QPushButton("🔄 Reload")
|
||||
self.reload_lua_btn.setEnabled(False)
|
||||
self.reload_lua_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #2a475e;
|
||||
color: #c6d1db;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
padding: 5px 15px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #3e5c78;
|
||||
}
|
||||
QPushButton:disabled {
|
||||
background-color: #1b2838;
|
||||
color: #4e5a66;
|
||||
}
|
||||
""")
|
||||
self.reload_lua_btn.clicked.connect(self.reload_lua_script)
|
||||
editor_toolbar.addWidget(self.reload_lua_btn)
|
||||
|
||||
editor_layout.addLayout(editor_toolbar)
|
||||
|
||||
# Code editor
|
||||
self.lua_editor = QPlainTextEdit()
|
||||
self.lua_editor.setPlaceholderText("Select a game to edit its Lua script...")
|
||||
editor_font = QFont("Courier New")
|
||||
editor_font.setPointSize(10)
|
||||
self.lua_editor.setFont(editor_font)
|
||||
self.lua_editor.setStyleSheet("""
|
||||
QPlainTextEdit {
|
||||
background-color: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
border: 1px solid #0e1216;
|
||||
border-radius: 3px;
|
||||
padding: 5px;
|
||||
}
|
||||
""")
|
||||
self.lua_editor.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap)
|
||||
self.lua_editor.textChanged.connect(self.on_lua_text_changed)
|
||||
|
||||
# Apply syntax highlighting
|
||||
self.lua_highlighter = LuaSyntaxHighlighter(self.lua_editor.document())
|
||||
|
||||
editor_layout.addWidget(self.lua_editor)
|
||||
|
||||
self.tab_widget.addTab(editor_container, "Lua Script Editor")
|
||||
|
||||
# Console Output Tab
|
||||
console_container = QWidget()
|
||||
console_container.setStyleSheet("background-color: #1b2838;")
|
||||
console_layout = QVBoxLayout(console_container)
|
||||
console_layout.setContentsMargins(10, 10, 10, 10)
|
||||
|
||||
@@ -667,7 +840,6 @@ def gui(args: argparse.Namespace) -> None:
|
||||
|
||||
self.console = QTextEdit()
|
||||
self.console.setReadOnly(True)
|
||||
self.console.setMinimumHeight(250)
|
||||
console_font = QFont("Courier New")
|
||||
console_font.setPointSize(9)
|
||||
self.console.setFont(console_font)
|
||||
@@ -681,7 +853,9 @@ def gui(args: argparse.Namespace) -> None:
|
||||
""")
|
||||
console_layout.addWidget(self.console)
|
||||
|
||||
right_layout.addWidget(console_container)
|
||||
self.tab_widget.addTab(console_container, "Console Output")
|
||||
|
||||
right_layout.addWidget(self.tab_widget)
|
||||
|
||||
main_layout.addWidget(right_panel, 1)
|
||||
|
||||
@@ -767,11 +941,107 @@ def gui(args: argparse.Namespace) -> None:
|
||||
self.game_title.setText(game["name"])
|
||||
self.game_description.setText(game["description"])
|
||||
self.play_btn.setEnabled(True)
|
||||
|
||||
# Load the Lua script for this game
|
||||
self.load_lua_script()
|
||||
else:
|
||||
self.current_game = None
|
||||
self.game_title.setText("Select a game")
|
||||
self.game_description.setText("")
|
||||
self.play_btn.setEnabled(False)
|
||||
self.lua_editor.clear()
|
||||
self.lua_file_label.setText("No file loaded")
|
||||
self.save_lua_btn.setEnabled(False)
|
||||
self.reload_lua_btn.setEnabled(False)
|
||||
|
||||
def load_lua_script(self):
|
||||
"""Load the Lua script associated with the current game"""
|
||||
if not self.current_game:
|
||||
return
|
||||
|
||||
import json
|
||||
|
||||
try:
|
||||
# Read the config file to get the lua_script path
|
||||
config_file = self.current_game.get("config_file", "")
|
||||
with open(config_file, 'r') as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
lua_script_path = config_data.get("lua_script", "")
|
||||
if not lua_script_path:
|
||||
self.lua_editor.setPlainText("# No Lua script specified in config")
|
||||
self.lua_file_label.setText("No Lua script found")
|
||||
self.save_lua_btn.setEnabled(False)
|
||||
self.reload_lua_btn.setEnabled(False)
|
||||
return
|
||||
|
||||
# Load the Lua script
|
||||
lua_file = Path(lua_script_path)
|
||||
if lua_file.exists():
|
||||
with open(lua_file, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# Block signals to prevent marking as modified
|
||||
self.lua_editor.blockSignals(True)
|
||||
self.lua_editor.setPlainText(content)
|
||||
self.lua_editor.blockSignals(False)
|
||||
|
||||
self.lua_file_label.setText(str(lua_file))
|
||||
self.current_lua_file = lua_file
|
||||
self.lua_modified = False
|
||||
self.save_lua_btn.setEnabled(False)
|
||||
self.reload_lua_btn.setEnabled(True)
|
||||
else:
|
||||
self.lua_editor.setPlainText(f"# Lua script not found: {lua_script_path}")
|
||||
self.lua_file_label.setText(f"File not found: {lua_script_path}")
|
||||
self.save_lua_btn.setEnabled(False)
|
||||
self.reload_lua_btn.setEnabled(False)
|
||||
except Exception as e:
|
||||
self.lua_editor.setPlainText(f"# Error loading Lua script: {e}")
|
||||
self.lua_file_label.setText("Error loading script")
|
||||
self.save_lua_btn.setEnabled(False)
|
||||
self.reload_lua_btn.setEnabled(False)
|
||||
|
||||
def on_lua_text_changed(self):
|
||||
"""Handle Lua editor text changes"""
|
||||
if hasattr(self, 'current_lua_file'):
|
||||
self.lua_modified = True
|
||||
self.save_lua_btn.setEnabled(True)
|
||||
|
||||
def save_lua_script(self):
|
||||
"""Save the current Lua script"""
|
||||
if not hasattr(self, 'current_lua_file'):
|
||||
return
|
||||
|
||||
try:
|
||||
with open(self.current_lua_file, 'w') as f:
|
||||
f.write(self.lua_editor.toPlainText())
|
||||
|
||||
self.lua_modified = False
|
||||
self.save_lua_btn.setEnabled(False)
|
||||
self.log(f"✓ Saved {self.current_lua_file}")
|
||||
|
||||
# Switch to console tab to show the message
|
||||
self.tab_widget.setCurrentIndex(1)
|
||||
except Exception as e:
|
||||
self.log(f"❌ Error saving {self.current_lua_file}: {e}")
|
||||
self.tab_widget.setCurrentIndex(1)
|
||||
|
||||
def reload_lua_script(self):
|
||||
"""Reload the Lua script from disk"""
|
||||
if self.lua_modified:
|
||||
# Warn user about unsaved changes
|
||||
from PyQt6.QtWidgets import QMessageBox
|
||||
reply = QMessageBox.question(
|
||||
self, 'Unsaved Changes',
|
||||
'You have unsaved changes. Reload anyway?',
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
QMessageBox.StandardButton.No
|
||||
)
|
||||
if reply == QMessageBox.StandardButton.No:
|
||||
return
|
||||
|
||||
self.load_lua_script()
|
||||
|
||||
def play_game(self):
|
||||
"""Launch the selected game"""
|
||||
|
||||
Reference in New Issue
Block a user