EG/demo/gui_editor_window.py
2025-12-12 16:16:15 +08:00

1008 lines
38 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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()