Skip to content

Commit 6e07a4f

Browse files
Add PySide6 support for macOS Nuitka compatibility
- Create Qt compatibility layer (core/qt_compat.py) - Update all PyQt6 imports to use compatibility layer - Use PySide6 on macOS, PyQt6 on Windows/Linux - Add PySide6 to requirements.txt - Update macOS build workflow to use pyside6 plugin
1 parent f7111fe commit 6e07a4f

19 files changed

Lines changed: 132 additions & 67 deletions

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,14 @@ jobs:
169169
170170
- name: Build with Nuitka
171171
run: |
172-
python -m nuitka --standalone --jobs=$(sysctl -n hw.ncpu) \
172+
QT_BINDING=pyside6 python -m nuitka --standalone --jobs=$(sysctl -n hw.ncpu) \
173173
--output-filename=SexyUIEditor --output-dir=dist \
174174
--company-name=StackAndPointer --product-name=SexyUIEditor \
175175
--file-description="SexyUI & PVZ UI Editor" \
176176
--copyright="Copyright (C) 2026 StackAndPointer" \
177177
--file-version=1.2.0.0 --product-version=1.2.0.0 \
178178
--macos-app-icon=resources/icons/icon.png \
179-
--enable-plugin=pyqt6 \
179+
--enable-plugin=pyside6 \
180180
--include-package=core --include-package=ui \
181181
--include-data-dir=i18n=i18n \
182182
--include-data-dir=resources=resources \

core/i18n.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import json
33
import os
44
import locale
5-
from PyQt6.QtCore import pyqtSignal, QObject
5+
from core.qt_compat import Signal, QObject, QSettings
66

77
_I18N_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "i18n")
88

@@ -16,7 +16,7 @@ class I18nManager(QObject):
1616
_instance = None
1717
_translations = {}
1818
_current_locale = "zh_CN"
19-
locale_changed = pyqtSignal(str)
19+
locale_changed = Signal(str)
2020

2121
@classmethod
2222
def instance(cls):
@@ -40,12 +40,10 @@ def detect_system_locale(self) -> str:
4040
return "en"
4141

4242
def get_saved_locale(self) -> str:
43-
from PyQt6.QtCore import QSettings
4443
settings = QSettings("SexyUIEditor", "SexyUIEditor")
4544
return settings.value("language", "", type=str)
4645

4746
def save_locale(self, locale_name: str):
48-
from PyQt6.QtCore import QSettings
4947
settings = QSettings("SexyUIEditor", "SexyUIEditor")
5048
settings.setValue("language", locale_name)
5149

core/qt_compat.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Qt Compatibility Layer
4+
5+
This module provides a unified interface for PyQt6 and PySide6,
6+
allowing the application to work on macOS where Nuitka only supports PySide6.
7+
8+
Usage:
9+
from core.qt_compat import QtWidgets, QtCore, QtGui, Signal
10+
11+
On Windows/Linux: Uses PyQt6
12+
On macOS: Uses PySide6 (for Nuitka compatibility)
13+
"""
14+
15+
import sys
16+
import os
17+
18+
# Force PySide6 on macOS for Nuitka compatibility
19+
# Can be overridden with environment variable QT_BINDING
20+
FORCE_PYSIDE6 = sys.platform == "darwin"
21+
QT_BINDING = os.environ.get("QT_BINDING", "auto")
22+
23+
if QT_BINDING == "pyside6" or (QT_BINDING == "auto" and FORCE_PYSIDE6):
24+
# Use PySide6
25+
from PySide6.QtWidgets import *
26+
from PySide6.QtCore import *
27+
from PySide6.QtGui import *
28+
29+
# PySide6 uses Signal instead of pyqtSignal
30+
Signal = Signal
31+
Slot = Slot
32+
Property = Property
33+
34+
QT_BINDING_NAME = "PySide6"
35+
else:
36+
# Use PyQt6 (default for Windows/Linux)
37+
from PyQt6.QtWidgets import *
38+
from PyQt6.QtCore import *
39+
from PyQt6.QtGui import *
40+
41+
# PyQt6 uses pyqtSignal, map to Signal for compatibility
42+
Signal = pyqtSignal
43+
Slot = pyqtSlot
44+
Property = pyqtProperty
45+
46+
QT_BINDING_NAME = "PyQt6"
47+
48+
# Export common classes that might be imported directly
49+
__all__ = [
50+
# QtWidgets
51+
"QApplication", "QWidget", "QMainWindow", "QDialog", "QMessageBox",
52+
"QVBoxLayout", "QHBoxLayout", "QGridLayout", "QScrollArea",
53+
"QPushButton", "QLabel", "QLineEdit", "QTextEdit", "QComboBox",
54+
"QCheckBox", "QTreeWidget", "QTreeWidgetItem", "QTabWidget",
55+
"QFileDialog", "QMenu", "QMenuBar", "QToolBar", "QStatusBar",
56+
"QSplitter", "QGroupBox", "QSpinBox", "QDoubleSpinBox",
57+
"QSlider", "QProgressBar", "QListWidget", "QTableWidget",
58+
"QAction", "QActionGroup", "QSizePolicy",
59+
60+
# QtCore
61+
"Qt", "QObject", "QSize", "QPoint", "QRect", "QSettings",
62+
"QTimer", "QEvent", "QMimeData", "QRegularExpression",
63+
"Signal", "Slot", "Property",
64+
65+
# QtGui
66+
"QPainter", "QPen", "QBrush", "QColor", "QFont", "QPixmap",
67+
"QIcon", "QImage", "QDrag", "QCursor", "QKeySequence",
68+
"QSyntaxHighlighter", "QTextCharFormat",
69+
"QMouseEvent", "QWheelEvent", "QDragEnterEvent", "QDropEvent",
70+
71+
# Module info
72+
"QT_BINDING_NAME",
73+
]

main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: utf-8 -*-
22
import sys
33
import os
4-
from PyQt6.QtWidgets import QApplication
4+
from core.qt_compat import QApplication
55

66
from ui.main_window import MainWindow
77
from core.i18n import I18nManager

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
PyQt6>=6.4.0
2+
PySide6>=6.4.0

ui/canvas.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# -*- coding: utf-8 -*-
22
import os
3-
from PyQt6.QtWidgets import QWidget, QScrollArea
4-
from PyQt6.QtCore import Qt, pyqtSignal, QPoint, QRect, QSize, QEvent
5-
from PyQt6.QtGui import QPainter, QPen, QBrush, QColor, QFont, QPixmap, QMouseEvent, QWheelEvent
3+
from core.qt_compat import (
4+
QWidget, QScrollArea, Qt, Signal, QPoint, QRect, QSize, QEvent,
5+
QPainter, QPen, QBrush, QColor, QFont, QPixmap, QMouseEvent, QWheelEvent
6+
)
67
from core.project import Project, WidgetInstance
78
from core.component_registry import ComponentRegistry
89
from core.resource_manager import ResourceManager
@@ -57,10 +58,10 @@ def _on_context_menu(self, pos):
5758

5859

5960
class DesignCanvas(QScrollArea):
60-
widget_selected = pyqtSignal(str)
61-
widget_moved = pyqtSignal(str, int, int)
62-
widget_resized = pyqtSignal(str, int, int)
63-
widget_context_menu = pyqtSignal(str, QPoint)
61+
widget_selected = Signal(str)
62+
widget_moved = Signal(str, int, int)
63+
widget_resized = Signal(str, int, int)
64+
widget_context_menu = Signal(str, QPoint)
6465

6566
def __init__(self, project: Project, parent=None):
6667
super().__init__(parent)

ui/code_view.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# -*- coding: utf-8 -*-
22
import os
3-
from PyQt6.QtWidgets import QDialog, QVBoxLayout, QTabWidget, QTextEdit, QPushButton, QHBoxLayout, QFileDialog, QComboBox, QLabel, QMessageBox
4-
from PyQt6.QtGui import QFont, QSyntaxHighlighter, QTextCharFormat, QColor
5-
from PyQt6.QtCore import QRegularExpression, QSettings
3+
from core.qt_compat import (
4+
QDialog, QVBoxLayout, QTabWidget, QTextEdit, QPushButton, QHBoxLayout,
5+
QFileDialog, QComboBox, QLabel, QMessageBox, QFont, QSyntaxHighlighter,
6+
QTextCharFormat, QColor, QRegularExpression, QSettings
7+
)
68
from core.i18n import tr
79
from ui.dark_titlebar import set_transparent_titlebar
810

ui/custom_component_dialog.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@
33
Custom Component Dialog
44
Dialog for creating and editing custom extension components.
55
"""
6-
from PyQt6.QtWidgets import (
6+
from core.qt_compat import (
77
QDialog, QVBoxLayout, QHBoxLayout, QFormLayout,
88
QLineEdit, QTextEdit, QPushButton, QCheckBox,
99
QFileDialog, QMessageBox, QListWidget, QListWidgetItem,
10-
QComboBox, QGroupBox, QLabel, QWidget, QSplitter
10+
QComboBox, QGroupBox, QLabel, QWidget, QSplitter, Qt, QIcon
1111
)
12-
from PyQt6.QtCore import Qt
13-
from PyQt6.QtGui import QIcon
1412

1513
from core.i18n import tr
1614
from core.extension_manager import ExtensionManager, ExtensionComponentDef, create_default_properties

ui/dark_titlebar.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
import sys
33
import ctypes
44
from ctypes import c_int, byref, sizeof
5-
from PyQt6.QtWidgets import QWidget
6-
from PyQt6.QtCore import QTimer
5+
from core.qt_compat import QWidget, QTimer
76

87

98
def set_transparent_titlebar(widget: QWidget):

ui/event_config.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
# -*- coding: utf-8 -*-
2-
from PyQt6.QtWidgets import (
2+
from core.qt_compat import (
33
QDialog, QVBoxLayout, QHBoxLayout, QListWidget, QListWidgetItem,
44
QPushButton, QLabel, QMenu, QComboBox, QLineEdit, QFormLayout,
5-
QWidget, QAbstractItemView, QMessageBox
5+
QWidget, QAbstractItemView, QMessageBox, Qt, QTimer, QCursor
66
)
7-
from PyQt6.QtCore import Qt, QTimer
8-
from PyQt6.QtGui import QCursor
97
from core.i18n import tr
108
from core.project import WidgetInstance, EventAction, Project
119
from core.predefined_actions import (

0 commit comments

Comments
 (0)