1008 lines
38 KiB
Python
1008 lines
38 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
独立的GUI编辑器窗口
|
||
当主程序进入GUI编辑模式时,弹出此窗口进行GUI编辑操作
|
||
"""
|
||
|
||
import warnings
|
||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||
|
||
import sys
|
||
import os
|
||
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
|
||
QLabel, QGroupBox, QGridLayout, QLineEdit,
|
||
QDoubleSpinBox, QSpinBox, QTextEdit, QComboBox,
|
||
QCheckBox, QSlider, QColorDialog, QFrame,
|
||
QSplitter, QTreeWidget, QTreeWidgetItem,
|
||
QFormLayout, QScrollArea, QMessageBox)
|
||
from PyQt5.QtCore import Qt, pyqtSignal, QTimer
|
||
from PyQt5.QtGui import QFont, QColor, QPalette
|
||
|
||
# 导入Panda3D相关模块
|
||
from direct.showbase.ShowBase import ShowBase
|
||
from direct.gui.DirectGui import *
|
||
from panda3d.core import *
|
||
|
||
class GUIPreviewWindow:
|
||
"""独立的Panda3D GUI预览窗口"""
|
||
def __init__(self):
|
||
self.preview_base = None
|
||
self.chinese_font = None
|
||
self.gui_elements = []
|
||
self.welcome_text = None
|
||
self.tip_text = None
|
||
self.setupPanda3D()
|
||
|
||
def setupPanda3D(self):
|
||
"""设置独立的Panda3D预览窗口"""
|
||
try:
|
||
# 配置Panda3D设置 - 创建独立窗口
|
||
loadPrcFileData("", """
|
||
win-size 600 400
|
||
window-title GUI Preview - 独立预览窗口
|
||
framebuffer-multisample 1
|
||
multisamples 2
|
||
audio-library-name null
|
||
notify-level-audio error
|
||
show-frame-rate-meter 0
|
||
want-pstats 0
|
||
""")
|
||
|
||
# 创建ShowBase实例
|
||
self.preview_base = ShowBase()
|
||
|
||
# 设置背景色
|
||
self.preview_base.setBackgroundColor(0.2, 0.2, 0.3)
|
||
|
||
# 调整相机位置(主要用于查看2D GUI)
|
||
self.preview_base.cam.setPos(0, -10, 0)
|
||
self.preview_base.cam.lookAt(0, 0, 0)
|
||
|
||
# 尝试加载中文字体
|
||
try:
|
||
self.chinese_font = self.preview_base.loader.loadFont('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc')
|
||
if not self.chinese_font:
|
||
print("预览窗口: 无法加载中文字体,将使用默认字体")
|
||
except:
|
||
print("预览窗口: 无法加载中文字体,将使用默认字体")
|
||
self.chinese_font = None
|
||
|
||
# 创建说明文本
|
||
self.createInfoText()
|
||
|
||
print("✓ 独立GUI预览窗口已创建")
|
||
print("这是一个独立的Panda3D窗口,可以正确显示2D GUI元素")
|
||
|
||
except Exception as e:
|
||
print(f"创建独立预览窗口失败: {str(e)}")
|
||
|
||
def createInfoText(self):
|
||
"""创建说明文本"""
|
||
# 创建欢迎信息
|
||
self.welcome_text = OnscreenText(
|
||
text="GUI预览窗口\n\n这是独立的Panda3D窗口\n在GUI编辑器中创建的元素\n会在这里正确显示\n\n特别是2D GUI元素",
|
||
pos=(0, 0.2),
|
||
scale=0.08,
|
||
fg=(1, 1, 1, 1),
|
||
align=TextNode.ACenter,
|
||
font=self.chinese_font if self.chinese_font else None
|
||
)
|
||
|
||
# 创建提示信息
|
||
self.tip_text = OnscreenText(
|
||
text="提示: 这个窗口独立于Qt,可以正确渲染DirectGUI元素",
|
||
pos=(0, -0.7),
|
||
scale=0.05,
|
||
fg=(0.8, 0.8, 0.8, 1),
|
||
align=TextNode.ACenter,
|
||
font=self.chinese_font if self.chinese_font else None
|
||
)
|
||
|
||
# 创建操作说明
|
||
self.control_text = OnscreenText(
|
||
text="操作说明:\n• 鼠标滚轮: 缩放视图\n• 右键拖拽: 旋转相机",
|
||
pos=(-0.9, -0.3),
|
||
scale=0.04,
|
||
fg=(0.7, 0.7, 1, 1),
|
||
align=TextNode.ALeft,
|
||
font=self.chinese_font if self.chinese_font else None
|
||
)
|
||
|
||
# 绑定鼠标控制
|
||
self.preview_base.accept("wheel_up", self.zoomIn)
|
||
self.preview_base.accept("wheel_down", self.zoomOut)
|
||
|
||
def zoomIn(self):
|
||
"""放大视图"""
|
||
pos = self.preview_base.cam.getPos()
|
||
forward = self.preview_base.cam.getQuat().getForward()
|
||
self.preview_base.cam.setPos(pos + forward * 2)
|
||
|
||
def zoomOut(self):
|
||
"""缩小视图"""
|
||
pos = self.preview_base.cam.getPos()
|
||
forward = self.preview_base.cam.getQuat().getForward()
|
||
self.preview_base.cam.setPos(pos - forward * 2)
|
||
|
||
def clearGUIElements(self):
|
||
"""清空所有GUI元素"""
|
||
# 移除所有已创建的GUI元素
|
||
for gui_element in self.gui_elements:
|
||
try:
|
||
if hasattr(gui_element, 'removeNode'):
|
||
gui_element.removeNode()
|
||
elif hasattr(gui_element, 'destroy'):
|
||
gui_element.destroy()
|
||
except:
|
||
pass
|
||
|
||
self.gui_elements.clear()
|
||
|
||
# 恢复说明文本
|
||
if not self.welcome_text:
|
||
self.createInfoText()
|
||
|
||
def updatePreview(self, gui_elements_info):
|
||
"""更新预览显示"""
|
||
try:
|
||
# 清除说明文本(如果存在GUI元素)
|
||
if gui_elements_info and self.welcome_text:
|
||
self.welcome_text.removeNode()
|
||
self.tip_text.removeNode()
|
||
self.control_text.removeNode()
|
||
self.welcome_text = None
|
||
self.tip_text = None
|
||
self.control_text = None
|
||
elif not gui_elements_info and not self.welcome_text:
|
||
# 如果没有GUI元素了,重新创建说明文本
|
||
self.createInfoText()
|
||
|
||
except Exception as e:
|
||
print(f"更新预览失败: {str(e)}")
|
||
|
||
def createElementInPreview(self, gui_type, params):
|
||
"""在预览窗口中创建GUI元素"""
|
||
try:
|
||
if not self.preview_base:
|
||
return None
|
||
|
||
element = None
|
||
|
||
if gui_type == "button":
|
||
# 创建按钮 - 使用适合预览的坐标
|
||
gui_pos = (params['pos'][0] * 0.08, 0, params['pos'][2] * 0.08)
|
||
element = DirectButton(
|
||
text=params['text'],
|
||
pos=gui_pos,
|
||
scale=params['scale'],
|
||
frameColor=(0.2, 0.6, 0.8, 1),
|
||
text_font=self.chinese_font if self.chinese_font else None,
|
||
command=lambda: print(f"预览按钮点击: {params['text']}")
|
||
)
|
||
print(f"预览窗口创建按钮: {params['text']} at {gui_pos}")
|
||
|
||
elif gui_type == "label":
|
||
# 创建标签
|
||
gui_pos = (params['pos'][0] * 0.08, 0, params['pos'][2] * 0.08)
|
||
element = DirectLabel(
|
||
text=params['text'],
|
||
pos=gui_pos,
|
||
scale=params['scale'],
|
||
frameColor=(0, 0, 0, 0),
|
||
text_fg=(1, 1, 1, 1),
|
||
text_font=self.chinese_font if self.chinese_font else None
|
||
)
|
||
print(f"预览窗口创建标签: {params['text']} at {gui_pos}")
|
||
|
||
elif gui_type == "entry":
|
||
# 创建输入框
|
||
gui_pos = (params['pos'][0] * 0.08, 0, params['pos'][2] * 0.08)
|
||
element = DirectEntry(
|
||
text="",
|
||
pos=gui_pos,
|
||
scale=params['scale'],
|
||
initialText=params['text'],
|
||
numLines=1,
|
||
width=12,
|
||
focus=0,
|
||
command=lambda text: print(f"预览输入框输入: {text}")
|
||
)
|
||
print(f"预览窗口创建输入框: {params['text']} at {gui_pos}")
|
||
|
||
elif gui_type == "slider":
|
||
# 创建滑块
|
||
gui_pos = (params['pos'][0] * 0.08, 0, params['pos'][2] * 0.08)
|
||
element = DirectSlider(
|
||
pos=gui_pos,
|
||
scale=params['scale'],
|
||
range=(0, 100),
|
||
value=50,
|
||
frameColor=(0.6, 0.6, 0.6, 1),
|
||
thumbColor=(0.2, 0.8, 0.2, 1),
|
||
command=lambda: print(f"预览滑块值变化")
|
||
)
|
||
print(f"预览窗口创建滑块: {params['text']} at {gui_pos}")
|
||
|
||
elif gui_type == "3d_text":
|
||
# 创建3D文本
|
||
textNode = TextNode(f'preview-3d-text-{len(self.gui_elements)}')
|
||
textNode.setText(params['text'])
|
||
textNode.setAlign(TextNode.ACenter)
|
||
if self.chinese_font:
|
||
textNode.setFont(self.chinese_font)
|
||
|
||
element = self.preview_base.render.attachNewNode(textNode)
|
||
element.setPos(*params['pos'])
|
||
element.setScale(params['scale'])
|
||
element.setColor(1, 1, 0, 1)
|
||
element.setBillboardAxis()
|
||
print(f"预览窗口创建3D文本: {params['text']} at {params['pos']}")
|
||
|
||
elif gui_type == "virtual_screen":
|
||
# 创建虚拟屏幕
|
||
cm = CardMaker(f"preview-virtual-screen-{len(self.gui_elements)}")
|
||
size = params.get('size', (2, 1))
|
||
cm.setFrame(-size[0]/2, size[0]/2, -size[1]/2, size[1]/2)
|
||
element = self.preview_base.render.attachNewNode(cm.generate())
|
||
element.setPos(*params['pos'])
|
||
element.setColor(0.2, 0.2, 0.2, 0.8)
|
||
element.setTransparency(TransparencyAttrib.MAlpha)
|
||
|
||
# 在虚拟屏幕上添加文本
|
||
screenText = TextNode(f'preview-screen-text-{len(self.gui_elements)}')
|
||
screenText.setText(params['text'])
|
||
screenText.setAlign(TextNode.ACenter)
|
||
if self.chinese_font:
|
||
screenText.setFont(self.chinese_font)
|
||
screenTextNP = element.attachNewNode(screenText)
|
||
screenTextNP.setPos(0, 0.01, 0)
|
||
screenTextNP.setScale(0.3)
|
||
screenTextNP.setColor(0, 1, 0, 1)
|
||
print(f"预览窗口创建虚拟屏幕: {params['text']} at {params['pos']}")
|
||
|
||
if element:
|
||
self.gui_elements.append(element)
|
||
return element
|
||
|
||
except Exception as e:
|
||
print(f"在预览窗口中创建GUI元素失败: {str(e)}")
|
||
|
||
return None
|
||
|
||
def refreshAllElements(self, elements_info):
|
||
"""刷新所有元素"""
|
||
# 清空现有元素
|
||
self.clearGUIElements()
|
||
|
||
# 重新创建所有元素
|
||
for element_info in elements_info:
|
||
self.createElementInPreview(element_info['type'], element_info['params'])
|
||
|
||
# 更新预览状态
|
||
self.updatePreview(elements_info)
|
||
|
||
def destroy(self):
|
||
"""销毁预览窗口"""
|
||
try:
|
||
if self.preview_base:
|
||
self.clearGUIElements()
|
||
self.preview_base.destroy()
|
||
self.preview_base = None
|
||
print("预览窗口已销毁")
|
||
except Exception as e:
|
||
print(f"销毁预览窗口失败: {str(e)}")
|
||
|
||
class GUIEditorWindow(QWidget):
|
||
"""独立的GUI编辑器窗口"""
|
||
|
||
# 信号定义
|
||
gui_created = pyqtSignal(str, dict) # GUI类型, 参数字典
|
||
gui_modified = pyqtSignal(object, str, object) # GUI对象, 属性名, 新值
|
||
gui_deleted = pyqtSignal(object) # GUI对象
|
||
editor_closed = pyqtSignal() # 编辑器关闭
|
||
|
||
def __init__(self, parent=None, world=None):
|
||
super().__init__(parent)
|
||
self.world = world # 主程序的world对象
|
||
self.gui_elements = [] # 当前编辑的GUI元素列表
|
||
self.selected_element = None # 当前选中的GUI元素
|
||
self.preview_window = None # 独立的预览窗口
|
||
|
||
self.setupUI()
|
||
self.setupConnections()
|
||
|
||
# 设置窗口属性
|
||
self.setWindowTitle("GUI编辑器 - 独立预览")
|
||
self.setMinimumSize(800, 600)
|
||
self.resize(1000, 700)
|
||
|
||
# 应用深色主题
|
||
self.applyDarkTheme()
|
||
|
||
# 创建独立预览窗口
|
||
self.createPreviewWindow()
|
||
|
||
# 从主程序同步现有的GUI元素
|
||
self.syncGUIElements()
|
||
|
||
print("GUI编辑器窗口已创建")
|
||
|
||
def createPreviewWindow(self):
|
||
"""创建独立的预览窗口"""
|
||
try:
|
||
self.preview_window = GUIPreviewWindow()
|
||
print("独立预览窗口已创建")
|
||
except Exception as e:
|
||
print(f"创建预览窗口失败: {str(e)}")
|
||
|
||
def setupUI(self):
|
||
"""设置用户界面"""
|
||
main_layout = QHBoxLayout(self)
|
||
|
||
# 创建分割器
|
||
splitter = QSplitter(Qt.Horizontal)
|
||
main_layout.addWidget(splitter)
|
||
|
||
# 左侧面板 - 工具和属性
|
||
left_panel = self.createLeftPanel()
|
||
splitter.addWidget(left_panel)
|
||
|
||
# 右侧面板 - 元素列表和预览说明
|
||
right_panel = self.createRightPanel()
|
||
splitter.addWidget(right_panel)
|
||
|
||
# 设置分割比例
|
||
splitter.setSizes([400, 600])
|
||
|
||
def createLeftPanel(self):
|
||
"""创建左侧工具面板"""
|
||
panel = QWidget()
|
||
layout = QVBoxLayout(panel)
|
||
|
||
# GUI创建工具组
|
||
tools_group = self.createToolsGroup()
|
||
layout.addWidget(tools_group)
|
||
|
||
# 属性编辑组
|
||
properties_group = self.createPropertiesGroup()
|
||
layout.addWidget(properties_group)
|
||
|
||
# 操作按钮组
|
||
actions_group = self.createActionsGroup()
|
||
layout.addWidget(actions_group)
|
||
|
||
layout.addStretch()
|
||
return panel
|
||
|
||
def createToolsGroup(self):
|
||
"""创建GUI工具组"""
|
||
group = QGroupBox("GUI创建工具")
|
||
layout = QGridLayout(group)
|
||
|
||
# 2D GUI工具
|
||
label_2d = QLabel("2D GUI元素")
|
||
label_2d.setStyleSheet("font-weight: bold; color: #4CAF50;")
|
||
layout.addWidget(label_2d, 0, 0, 1, 2)
|
||
|
||
# 按钮工具
|
||
btn_button = QPushButton("创建按钮")
|
||
btn_button.clicked.connect(lambda: self.createGUIElement("button"))
|
||
btn_button.setStyleSheet("QPushButton { background-color: #2196F3; color: white; padding: 8px; }")
|
||
layout.addWidget(btn_button, 1, 0)
|
||
|
||
# 标签工具
|
||
btn_label = QPushButton("创建标签")
|
||
btn_label.clicked.connect(lambda: self.createGUIElement("label"))
|
||
btn_label.setStyleSheet("QPushButton { background-color: #4CAF50; color: white; padding: 8px; }")
|
||
layout.addWidget(btn_label, 1, 1)
|
||
|
||
# 输入框工具
|
||
btn_entry = QPushButton("创建输入框")
|
||
btn_entry.clicked.connect(lambda: self.createGUIElement("entry"))
|
||
btn_entry.setStyleSheet("QPushButton { background-color: #FF9800; color: white; padding: 8px; }")
|
||
layout.addWidget(btn_entry, 2, 0)
|
||
|
||
# 滑块工具
|
||
btn_slider = QPushButton("创建滑块")
|
||
btn_slider.clicked.connect(lambda: self.createGUIElement("slider"))
|
||
btn_slider.setStyleSheet("QPushButton { background-color: #9C27B0; color: white; padding: 8px; }")
|
||
layout.addWidget(btn_slider, 2, 1)
|
||
|
||
# 3D GUI工具
|
||
label_3d = QLabel("3D GUI元素")
|
||
label_3d.setStyleSheet("font-weight: bold; color: #FF5722;")
|
||
layout.addWidget(label_3d, 3, 0, 1, 2)
|
||
|
||
# 3D文本工具
|
||
btn_3dtext = QPushButton("创建3D文本")
|
||
btn_3dtext.clicked.connect(lambda: self.createGUIElement("3d_text"))
|
||
btn_3dtext.setStyleSheet("QPushButton { background-color: #E91E63; color: white; padding: 8px; }")
|
||
layout.addWidget(btn_3dtext, 4, 0)
|
||
|
||
# 虚拟屏幕工具
|
||
btn_screen = QPushButton("创建虚拟屏幕")
|
||
btn_screen.clicked.connect(lambda: self.createGUIElement("virtual_screen"))
|
||
btn_screen.setStyleSheet("QPushButton { background-color: #607D8B; color: white; padding: 8px; }")
|
||
layout.addWidget(btn_screen, 4, 1)
|
||
|
||
return group
|
||
|
||
def createPropertiesGroup(self):
|
||
"""创建属性编辑组"""
|
||
group = QGroupBox("属性编辑")
|
||
layout = QVBoxLayout(group)
|
||
|
||
# 创建滚动区域
|
||
scroll = QScrollArea()
|
||
scroll.setWidgetResizable(True)
|
||
scroll.setMinimumHeight(200)
|
||
|
||
# 属性容器
|
||
self.properties_widget = QWidget()
|
||
self.properties_layout = QFormLayout(self.properties_widget)
|
||
scroll.setWidget(self.properties_widget)
|
||
|
||
layout.addWidget(scroll)
|
||
|
||
# 初始提示
|
||
self.showNoSelectionMessage()
|
||
|
||
return group
|
||
|
||
def createActionsGroup(self):
|
||
"""创建操作按钮组"""
|
||
group = QGroupBox("操作")
|
||
layout = QGridLayout(group)
|
||
|
||
# 删除按钮
|
||
btn_delete = QPushButton("删除元素")
|
||
btn_delete.clicked.connect(self.deleteSelectedElement)
|
||
btn_delete.setStyleSheet("QPushButton { background-color: #F44336; color: white; padding: 8px; }")
|
||
layout.addWidget(btn_delete, 0, 0)
|
||
|
||
# 复制按钮
|
||
btn_copy = QPushButton("复制元素")
|
||
btn_copy.clicked.connect(self.copySelectedElement)
|
||
btn_copy.setStyleSheet("QPushButton { background-color: #2196F3; color: white; padding: 8px; }")
|
||
layout.addWidget(btn_copy, 0, 1)
|
||
|
||
# 清空所有按钮
|
||
btn_clear = QPushButton("清空所有")
|
||
btn_clear.clicked.connect(self.clearAllElements)
|
||
btn_clear.setStyleSheet("QPushButton { background-color: #795548; color: white; padding: 8px; }")
|
||
layout.addWidget(btn_clear, 1, 0)
|
||
|
||
# 关闭编辑器按钮
|
||
btn_close = QPushButton("关闭编辑器")
|
||
btn_close.clicked.connect(self.close)
|
||
btn_close.setStyleSheet("QPushButton { background-color: #9E9E9E; color: white; padding: 8px; }")
|
||
layout.addWidget(btn_close, 1, 1)
|
||
|
||
return group
|
||
|
||
def createRightPanel(self):
|
||
"""创建右侧面板"""
|
||
panel = QWidget()
|
||
layout = QVBoxLayout(panel)
|
||
|
||
# GUI元素列表
|
||
elements_group = QGroupBox("GUI元素列表")
|
||
elements_layout = QVBoxLayout(elements_group)
|
||
|
||
self.elements_tree = QTreeWidget()
|
||
self.elements_tree.setHeaderLabel("GUI元素")
|
||
self.elements_tree.itemClicked.connect(self.onElementSelected)
|
||
elements_layout.addWidget(self.elements_tree)
|
||
|
||
layout.addWidget(elements_group)
|
||
|
||
# 预览说明区域
|
||
preview_group = QGroupBox("预览窗口说明")
|
||
preview_layout = QVBoxLayout(preview_group)
|
||
|
||
# 说明文本
|
||
info_label = QLabel("""
|
||
<b>独立预览窗口</b><br><br>
|
||
GUI元素会在独立的Panda3D窗口中显示,这样可以:<br>
|
||
• 正确渲染2D GUI元素(DirectGUI)<br>
|
||
• 避免Qt集成导致的显示问题<br>
|
||
• 提供更好的预览效果<br><br>
|
||
<b>操作方式:</b><br>
|
||
• 在左侧创建GUI元素<br>
|
||
• 在预览窗口中查看效果<br>
|
||
• 选择元素后在属性面板中编辑
|
||
""")
|
||
info_label.setWordWrap(True)
|
||
info_label.setStyleSheet("color: #CCCCCC; padding: 10px; background-color: #3c3c3c; border-radius: 5px;")
|
||
preview_layout.addWidget(info_label)
|
||
|
||
# 预览窗口控制按钮
|
||
btn_layout = QHBoxLayout()
|
||
|
||
btn_show_preview = QPushButton("显示预览窗口")
|
||
btn_show_preview.clicked.connect(self.showPreviewWindow)
|
||
btn_show_preview.setStyleSheet("QPushButton { background-color: #4CAF50; color: white; padding: 8px; }")
|
||
btn_layout.addWidget(btn_show_preview)
|
||
|
||
btn_refresh_preview = QPushButton("刷新预览")
|
||
btn_refresh_preview.clicked.connect(self.refreshPreview)
|
||
btn_refresh_preview.setStyleSheet("QPushButton { background-color: #2196F3; color: white; padding: 8px; }")
|
||
btn_layout.addWidget(btn_refresh_preview)
|
||
|
||
preview_layout.addLayout(btn_layout)
|
||
|
||
layout.addWidget(preview_group)
|
||
|
||
return panel
|
||
|
||
def showPreviewWindow(self):
|
||
"""显示预览窗口"""
|
||
if not self.preview_window:
|
||
self.createPreviewWindow()
|
||
|
||
# 确保预览窗口可见
|
||
if self.preview_window and self.preview_window.preview_base:
|
||
# 预览窗口应该已经是独立的,这里只是确保它在前台
|
||
try:
|
||
# 如果有窗口属性,将其置于前台
|
||
win = self.preview_window.preview_base.win
|
||
if win:
|
||
print("预览窗口已显示")
|
||
except:
|
||
pass
|
||
|
||
def refreshPreview(self):
|
||
"""刷新预览"""
|
||
if self.preview_window:
|
||
self.preview_window.refreshAllElements(self.gui_elements)
|
||
print("预览已刷新")
|
||
|
||
def setupConnections(self):
|
||
"""设置信号连接"""
|
||
pass
|
||
|
||
def syncGUIElements(self):
|
||
"""从主程序同步现有的GUI元素"""
|
||
if not self.world:
|
||
return
|
||
|
||
print("正在同步现有GUI元素...")
|
||
|
||
# 清空列表
|
||
self.gui_elements.clear()
|
||
self.elements_tree.clear()
|
||
|
||
# 获取主程序中的GUI元素
|
||
for gui_element in self.world.gui_elements:
|
||
try:
|
||
gui_type = gui_element.getTag("gui_type") if hasattr(gui_element, 'getTag') else "unknown"
|
||
gui_text = gui_element.getTag("gui_text") if hasattr(gui_element, 'getTag') else "GUI元素"
|
||
|
||
# 构建参数字典
|
||
params = {
|
||
'text': gui_text,
|
||
'pos': (0, 0, 0),
|
||
'scale': 0.1
|
||
}
|
||
|
||
if hasattr(gui_element, 'getPos'):
|
||
pos = gui_element.getPos()
|
||
params['pos'] = (pos.getX(), pos.getY(), pos.getZ())
|
||
|
||
if hasattr(gui_element, 'getScale'):
|
||
scale = gui_element.getScale()
|
||
params['scale'] = scale.getX()
|
||
|
||
self.addElementToList(gui_element, gui_type, params)
|
||
|
||
except Exception as e:
|
||
print(f"同步GUI元素失败: {str(e)}")
|
||
|
||
print(f"同步完成,共 {len(self.gui_elements)} 个GUI元素")
|
||
|
||
# 刷新预览
|
||
self.refreshPreview()
|
||
|
||
def createGUIElement(self, gui_type):
|
||
"""创建GUI元素"""
|
||
print(f"创建GUI元素: {gui_type}")
|
||
|
||
# 收集参数
|
||
params = self.collectGUIParameters(gui_type)
|
||
|
||
# 在主程序中创建
|
||
if self.world:
|
||
element = self.createElementInWorld(gui_type, params)
|
||
if element:
|
||
self.addElementToList(element, gui_type, params)
|
||
|
||
# 在预览窗口中创建
|
||
if self.preview_window:
|
||
self.preview_window.createElementInPreview(gui_type, params)
|
||
|
||
# 更新预览
|
||
self.updatePreview()
|
||
|
||
def collectGUIParameters(self, gui_type):
|
||
"""收集GUI创建参数"""
|
||
# 计算一个合适的位置,避免重叠
|
||
element_count = len(self.gui_elements)
|
||
x_offset = (element_count % 5) * 3
|
||
z_offset = (element_count // 5) * 2
|
||
|
||
# 默认参数
|
||
params = {
|
||
'pos': (x_offset, 0, z_offset),
|
||
'scale': 0.1,
|
||
'text': f'{gui_type}_{element_count}'
|
||
}
|
||
|
||
# 根据类型调整参数
|
||
if gui_type == "button":
|
||
params['text'] = f"按钮_{element_count}"
|
||
params['scale'] = 0.08
|
||
elif gui_type == "label":
|
||
params['text'] = f"标签_{element_count}"
|
||
params['scale'] = 0.06
|
||
elif gui_type == "entry":
|
||
params['text'] = f"输入框_{element_count}"
|
||
params['scale'] = 0.05
|
||
elif gui_type == "slider":
|
||
params['text'] = f"滑块_{element_count}"
|
||
params['scale'] = 0.3
|
||
params['range'] = (0, 100)
|
||
params['value'] = 50
|
||
elif gui_type == "3d_text":
|
||
params['text'] = f"3D文本_{element_count}"
|
||
params['pos'] = (x_offset, 5, z_offset)
|
||
params['scale'] = 0.5
|
||
elif gui_type == "virtual_screen":
|
||
params['text'] = f"屏幕_{element_count}"
|
||
params['pos'] = (x_offset, 8, z_offset)
|
||
params['size'] = (2, 1)
|
||
|
||
return params
|
||
|
||
def createElementInWorld(self, gui_type, params):
|
||
"""在主程序世界中创建GUI元素"""
|
||
try:
|
||
if not self.world:
|
||
return None
|
||
|
||
if gui_type == "button":
|
||
return self.world.createGUIButton(params['pos'], params['text'], params['scale'])
|
||
elif gui_type == "label":
|
||
return self.world.createGUILabel(params['pos'], params['text'], params['scale'])
|
||
elif gui_type == "entry":
|
||
return self.world.createGUIEntry(params['pos'], params['text'], params['scale'])
|
||
elif gui_type == "slider":
|
||
return self.world.createGUISlider(params['pos'], params['text'], params['scale'])
|
||
elif gui_type == "3d_text":
|
||
return self.world.createGUI3DText(params['pos'], params['text'], params['scale'])
|
||
elif gui_type == "virtual_screen":
|
||
return self.world.createGUIVirtualScreen(params['pos'], params.get('size', (2, 1)), params['text'])
|
||
|
||
except Exception as e:
|
||
print(f"创建GUI元素失败: {str(e)}")
|
||
QMessageBox.warning(self, "错误", f"创建GUI元素失败: {str(e)}")
|
||
|
||
return None
|
||
|
||
def addElementToList(self, element, gui_type, params):
|
||
"""添加元素到列表"""
|
||
# 添加到内部列表
|
||
element_info = {
|
||
'element': element,
|
||
'type': gui_type,
|
||
'params': params
|
||
}
|
||
self.gui_elements.append(element_info)
|
||
|
||
# 添加到树形控件
|
||
item = QTreeWidgetItem(self.elements_tree)
|
||
item.setText(0, f"{gui_type}: {params['text']}")
|
||
item.setData(0, Qt.UserRole, element_info)
|
||
|
||
# 自动选中新创建的元素
|
||
self.elements_tree.setCurrentItem(item)
|
||
self.onElementSelected(item, 0)
|
||
|
||
def onElementSelected(self, item, column):
|
||
"""元素被选中"""
|
||
element_info = item.data(0, Qt.UserRole)
|
||
if element_info:
|
||
self.selected_element = element_info
|
||
self.updatePropertiesPanel(element_info)
|
||
|
||
def updatePropertiesPanel(self, element_info):
|
||
"""更新属性面板"""
|
||
# 清空现有属性
|
||
self.clearPropertiesPanel()
|
||
|
||
element = element_info['element']
|
||
gui_type = element_info['type']
|
||
params = element_info['params']
|
||
|
||
# 通用属性
|
||
self.addPropertyEditor("类型", gui_type, read_only=True)
|
||
|
||
# 文本属性
|
||
if 'text' in params:
|
||
text_edit = QLineEdit(params['text'])
|
||
text_edit.textChanged.connect(lambda text: self.updateElementProperty('text', text))
|
||
self.properties_layout.addRow("文本:", text_edit)
|
||
|
||
# 位置属性
|
||
if hasattr(element, 'getPos'):
|
||
pos = element.getPos()
|
||
|
||
x_spin = QDoubleSpinBox()
|
||
x_spin.setRange(-1000, 1000)
|
||
x_spin.setValue(pos.getX())
|
||
x_spin.valueChanged.connect(lambda v: self.updateElementPosition('x', v))
|
||
self.properties_layout.addRow("位置 X:", x_spin)
|
||
|
||
y_spin = QDoubleSpinBox()
|
||
y_spin.setRange(-1000, 1000)
|
||
y_spin.setValue(pos.getY())
|
||
y_spin.valueChanged.connect(lambda v: self.updateElementPosition('y', v))
|
||
self.properties_layout.addRow("位置 Y:", y_spin)
|
||
|
||
z_spin = QDoubleSpinBox()
|
||
z_spin.setRange(-1000, 1000)
|
||
z_spin.setValue(pos.getZ())
|
||
z_spin.valueChanged.connect(lambda v: self.updateElementPosition('z', v))
|
||
self.properties_layout.addRow("位置 Z:", z_spin)
|
||
|
||
# 缩放属性
|
||
if hasattr(element, 'getScale'):
|
||
scale = element.getScale()
|
||
scale_spin = QDoubleSpinBox()
|
||
scale_spin.setRange(0.01, 10.0)
|
||
scale_spin.setSingleStep(0.1)
|
||
scale_spin.setValue(scale.getX())
|
||
scale_spin.valueChanged.connect(lambda v: self.updateElementScale(v))
|
||
self.properties_layout.addRow("缩放:", scale_spin)
|
||
|
||
def addPropertyEditor(self, label, value, read_only=False):
|
||
"""添加属性编辑器"""
|
||
if read_only:
|
||
editor = QLabel(str(value))
|
||
editor.setStyleSheet("color: gray;")
|
||
else:
|
||
editor = QLineEdit(str(value))
|
||
|
||
self.properties_layout.addRow(f"{label}:", editor)
|
||
|
||
def clearPropertiesPanel(self):
|
||
"""清空属性面板"""
|
||
while self.properties_layout.count():
|
||
child = self.properties_layout.takeAt(0)
|
||
if child.widget():
|
||
child.widget().deleteLater()
|
||
|
||
def showNoSelectionMessage(self):
|
||
"""显示未选中提示"""
|
||
self.clearPropertiesPanel()
|
||
label = QLabel("请选择一个GUI元素进行编辑")
|
||
label.setAlignment(Qt.AlignCenter)
|
||
label.setStyleSheet("color: gray; font-style: italic; padding: 20px;")
|
||
self.properties_layout.addRow(label)
|
||
|
||
def updateElementProperty(self, property_name, value):
|
||
"""更新元素属性"""
|
||
if not self.selected_element:
|
||
return
|
||
|
||
element = self.selected_element['element']
|
||
|
||
try:
|
||
if self.world:
|
||
success = self.world.editGUIElement(element, property_name, value)
|
||
if success:
|
||
self.selected_element['params'][property_name] = value
|
||
# 更新树形控件显示
|
||
current_item = self.elements_tree.currentItem()
|
||
if current_item and property_name == 'text':
|
||
gui_type = self.selected_element['type']
|
||
current_item.setText(0, f"{gui_type}: {value}")
|
||
|
||
# 刷新预览
|
||
self.refreshPreview()
|
||
except Exception as e:
|
||
print(f"更新属性失败: {str(e)}")
|
||
|
||
def updateElementPosition(self, axis, value):
|
||
"""更新元素位置"""
|
||
if not self.selected_element:
|
||
return
|
||
|
||
element = self.selected_element['element']
|
||
current_pos = element.getPos()
|
||
|
||
if axis == 'x':
|
||
new_pos = [value, current_pos.getY(), current_pos.getZ()]
|
||
elif axis == 'y':
|
||
new_pos = [current_pos.getX(), value, current_pos.getZ()]
|
||
elif axis == 'z':
|
||
new_pos = [current_pos.getX(), current_pos.getY(), value]
|
||
|
||
self.updateElementProperty('position', new_pos)
|
||
|
||
def updateElementScale(self, value):
|
||
"""更新元素缩放"""
|
||
self.updateElementProperty('scale', value)
|
||
|
||
def updatePreview(self):
|
||
"""更新预览显示"""
|
||
try:
|
||
if self.preview_window:
|
||
self.preview_window.updatePreview(self.gui_elements)
|
||
except Exception as e:
|
||
print(f"更新预览失败: {str(e)}")
|
||
|
||
def deleteSelectedElement(self):
|
||
"""删除选中的元素"""
|
||
if not self.selected_element:
|
||
QMessageBox.information(self, "提示", "请先选择要删除的元素")
|
||
return
|
||
|
||
# 确认删除
|
||
reply = QMessageBox.question(self, "确认删除",
|
||
"确定要删除选中的GUI元素吗?",
|
||
QMessageBox.Yes | QMessageBox.No)
|
||
|
||
if reply == QMessageBox.Yes:
|
||
element = self.selected_element['element']
|
||
|
||
# 从主程序中删除
|
||
if self.world:
|
||
success = self.world.deleteGUIElement(element)
|
||
if success:
|
||
# 从列表中移除
|
||
self.gui_elements.remove(self.selected_element)
|
||
|
||
# 从树形控件中移除
|
||
current_item = self.elements_tree.currentItem()
|
||
if current_item:
|
||
self.elements_tree.takeTopLevelItem(
|
||
self.elements_tree.indexOfTopLevelItem(current_item)
|
||
)
|
||
|
||
self.selected_element = None
|
||
self.showNoSelectionMessage()
|
||
|
||
# 刷新预览
|
||
self.refreshPreview()
|
||
|
||
def copySelectedElement(self):
|
||
"""复制选中的元素"""
|
||
if not self.selected_element:
|
||
QMessageBox.information(self, "提示", "请先选择要复制的元素")
|
||
return
|
||
|
||
element = self.selected_element['element']
|
||
gui_type = self.selected_element['type']
|
||
params = self.selected_element['params'].copy()
|
||
|
||
# 修改复制元素的参数
|
||
params['text'] = params['text'] + "_副本"
|
||
if 'pos' in params:
|
||
pos = list(params['pos'])
|
||
pos[0] += 1 # X轴偏移
|
||
params['pos'] = tuple(pos)
|
||
|
||
# 创建新元素
|
||
new_element = self.createElementInWorld(gui_type, params)
|
||
if new_element:
|
||
self.addElementToList(new_element, gui_type, params)
|
||
# 在预览窗口中创建
|
||
if self.preview_window:
|
||
self.preview_window.createElementInPreview(gui_type, params)
|
||
# 更新预览
|
||
self.updatePreview()
|
||
|
||
def clearAllElements(self):
|
||
"""清空所有元素"""
|
||
if not self.gui_elements:
|
||
QMessageBox.information(self, "提示", "没有可清空的元素")
|
||
return
|
||
|
||
# 确认清空
|
||
reply = QMessageBox.question(self, "确认清空",
|
||
f"确定要删除所有 {len(self.gui_elements)} 个GUI元素吗?",
|
||
QMessageBox.Yes | QMessageBox.No)
|
||
|
||
if reply == QMessageBox.Yes:
|
||
# 删除所有元素
|
||
for element_info in self.gui_elements.copy():
|
||
element = element_info['element']
|
||
if self.world:
|
||
self.world.deleteGUIElement(element)
|
||
|
||
# 清空列表
|
||
self.gui_elements.clear()
|
||
self.elements_tree.clear()
|
||
self.selected_element = None
|
||
self.showNoSelectionMessage()
|
||
|
||
# 刷新预览窗口
|
||
self.refreshPreview()
|
||
|
||
def applyDarkTheme(self):
|
||
"""应用深色主题"""
|
||
self.setStyleSheet("""
|
||
QWidget {
|
||
background-color: #2b2b2b;
|
||
color: #ffffff;
|
||
}
|
||
QGroupBox {
|
||
font-weight: bold;
|
||
border: 2px solid #555555;
|
||
border-radius: 8px;
|
||
margin-top: 1ex;
|
||
padding-top: 8px;
|
||
}
|
||
QGroupBox::title {
|
||
subcontrol-origin: margin;
|
||
left: 10px;
|
||
padding: 0 5px 0 5px;
|
||
}
|
||
QPushButton {
|
||
border: 1px solid #555555;
|
||
border-radius: 4px;
|
||
padding: 6px;
|
||
min-width: 80px;
|
||
}
|
||
QPushButton:hover {
|
||
border: 1px solid #777777;
|
||
}
|
||
QPushButton:pressed {
|
||
background-color: #404040;
|
||
}
|
||
QLineEdit, QDoubleSpinBox, QSpinBox, QComboBox {
|
||
border: 1px solid #555555;
|
||
border-radius: 4px;
|
||
padding: 4px;
|
||
background-color: #3c3c3c;
|
||
}
|
||
QTreeWidget {
|
||
border: 1px solid #555555;
|
||
background-color: #3c3c3c;
|
||
alternate-background-color: #404040;
|
||
}
|
||
QTextEdit {
|
||
border: 1px solid #555555;
|
||
background-color: #3c3c3c;
|
||
}
|
||
""")
|
||
|
||
def closeEvent(self, event):
|
||
"""窗口关闭事件"""
|
||
try:
|
||
# 销毁预览窗口
|
||
if self.preview_window:
|
||
self.preview_window.destroy()
|
||
self.preview_window = None
|
||
except Exception as e:
|
||
print(f"关闭预览窗口失败: {str(e)}")
|
||
|
||
self.editor_closed.emit()
|
||
super().closeEvent(event)
|
||
|
||
def main():
|
||
"""独立测试函数"""
|
||
from PyQt5.QtWidgets import QApplication
|
||
|
||
app = QApplication(sys.argv)
|
||
|
||
# 创建测试窗口
|
||
editor = GUIEditorWindow()
|
||
editor.show()
|
||
|
||
sys.exit(app.exec_())
|
||
|
||
if __name__ == "__main__":
|
||
main() |