界面修改以及显示
This commit is contained in:
@ -2,7 +2,7 @@ from PySide6.QtWidgets import QApplication, QMainWindow, QLabel, QWidget, QVBoxL
|
||||
from PySide6.QtGui import QPixmap
|
||||
from PySide6.QtCore import Qt, QPropertyAnimation, Property
|
||||
import os
|
||||
import resources.resource_rc
|
||||
import resources.resources_rc
|
||||
|
||||
# 拱形进度条
|
||||
class OverlapArcProgress(QWidget):
|
||||
@ -45,18 +45,18 @@ class OverlapArcProgress(QWidget):
|
||||
self.fg_label.setGeometry(0, self.total_height, self.bg_pixmap.width(), 0)
|
||||
|
||||
# ---------- 进度显示标签 ----------
|
||||
self.progress_label = QLabel(self)
|
||||
self.progress_label.setGeometry(217, 17, 121, 34)
|
||||
self.progress_label.setStyleSheet("""
|
||||
background-color: #444750;
|
||||
color: #A2EF4D;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
""")
|
||||
# 文本在标签内部居中显示
|
||||
self.progress_label.setAlignment(Qt.AlignCenter)
|
||||
self.progress_label.raise_() # 确保在前景上层(最上层)
|
||||
self.progress_label.setText("0%") # 初始文本
|
||||
# self.progress_label = QLabel(self)
|
||||
# self.progress_label.setGeometry(217, 17, 121, 34)
|
||||
# self.progress_label.setStyleSheet("""
|
||||
# background-color: #444750;
|
||||
# color: #A2EF4D;
|
||||
# font-size: 20px;
|
||||
# font-weight: bold;
|
||||
# """)
|
||||
# # 文本在标签内部居中显示
|
||||
# self.progress_label.setAlignment(Qt.AlignCenter)
|
||||
# self.progress_label.raise_() # 确保在前景上层(最上层)
|
||||
# self.progress_label.setText("0%") # 初始文本
|
||||
|
||||
# ---------- 进度属性 ----------
|
||||
@Property(int)
|
||||
@ -73,7 +73,7 @@ class OverlapArcProgress(QWidget):
|
||||
def update_foreground(self):
|
||||
"""通过调整前景标签的高度和Y坐标, 实现从底部向上填充的进度条"""
|
||||
# 同步更新进度标签文本
|
||||
self.progress_label.setText(f"{self._progress}%")
|
||||
# self.progress_label.setText(f"{self._progress}%")
|
||||
|
||||
# print(f"{self._progress}%")
|
||||
progress_ratio = self._progress / 100.0
|
||||
@ -105,90 +105,132 @@ class OverlapArcProgress(QWidget):
|
||||
class ArcProgressWidget(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# 加载MainWidget背景图
|
||||
# 加载ArcProgressWidget背景图
|
||||
# 添加了 拱3.png 作为背景图
|
||||
bg_img = ":/icons/images/拱3.png" # 需要修改为实际的图片的路径
|
||||
# self.setStyleSheet(f"background-image: url({bg_img}); background-repeat: no-repeat; background-position: center;")
|
||||
self.bg_pixmap = QPixmap(bg_img)
|
||||
# self.setStyleSheet("background-color: red;")
|
||||
|
||||
# # 设置控件widget大小与背景图一致
|
||||
self.setFixedSize(self.bg_pixmap.size())
|
||||
# self.bg_pixmap.size() 为 555x227
|
||||
# self.setFixedSize(self.bg_pixmap.size())
|
||||
# self.setFixedSize(555, 254)
|
||||
self.setFixedSize(555, 259)
|
||||
|
||||
self.setSizePolicy(
|
||||
QSizePolicy.Fixed, # 水平方向固定
|
||||
QSizePolicy.Fixed # 垂直方向固定
|
||||
)
|
||||
|
||||
# 创建背景标签
|
||||
background_label = QLabel(self)
|
||||
# 创建垂直主布局
|
||||
main_layout = QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0) # 去除边距
|
||||
main_layout.setSpacing(0) # 去除控件间距
|
||||
|
||||
# 1. 添加标题标签(保持居中)
|
||||
self.arc_title_label = QLabel("振捣模具车", self)
|
||||
self.arc_title_label.setFixedSize(555, 29)
|
||||
self.arc_title_label.setAlignment(Qt.AlignCenter)
|
||||
self.arc_title_label.setStyleSheet("""
|
||||
color: #0bffff;
|
||||
font-size: 18px;
|
||||
background-image: url(:/icons/images/文字标题底.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
margin: 4px auto;
|
||||
""")
|
||||
main_layout.addWidget(self.arc_title_label, alignment=Qt.AlignCenter)
|
||||
|
||||
# 2. 创建普通QWidget作为容器(大小与背景图一致)
|
||||
container = QWidget(self)
|
||||
container.setFixedSize(self.bg_pixmap.size()) # 容器大小=背景图大小
|
||||
|
||||
# 容器内设置背景图
|
||||
background_label = QLabel(container)
|
||||
background_label.setPixmap(self.bg_pixmap)
|
||||
background_label.setScaledContents(True)
|
||||
background_label.setFixedSize(self.bg_pixmap.size())
|
||||
background_label.setFixedSize(container.size())
|
||||
background_label.lower() # 背景置底,避免遮挡其他控件
|
||||
|
||||
# 3. 在容器内添加所有标签和拱形进度条(用setGeometry定位)
|
||||
# 创建拱形进度条控件
|
||||
arc_bg_img = ":/icons/images/拱2.png" # 拱进度条背景(空心) # 需要修改为实际的图片的路径
|
||||
arc_fg_img = ":/icons/images/拱1.png" # 拱进度条前景(实心) # 需要修改为实际的图片的路径
|
||||
self.arc_progress = OverlapArcProgress(arc_bg_img, arc_fg_img, self)
|
||||
|
||||
# 显示重量的标签,单位是 kg
|
||||
self.weight_label = QLabel(self)
|
||||
self.weight_label.setGeometry(217, 118, 118, 29)
|
||||
arc_bg_img = ":/icons/images/拱2.png" # 替换为实际路径
|
||||
arc_fg_img = ":/icons/images/拱1.png" # 替换为实际路径
|
||||
self.arc_progress = OverlapArcProgress(arc_bg_img, arc_fg_img, container)
|
||||
|
||||
# 显示环号的标签
|
||||
self.ring_number_label = QLabel("环号: 1", container)
|
||||
self.ring_number_label.setGeometry(98, 118, 116, 29) # 保持原坐标
|
||||
self.ring_number_label.setStyleSheet("""
|
||||
background-color: #143a6e;
|
||||
color: #16ffff;
|
||||
font-size: 18px;
|
||||
""")
|
||||
self.ring_number_label.setAlignment(Qt.AlignCenter)
|
||||
|
||||
# 显示重量的标签
|
||||
self.weight_label = QLabel("2000kg", container)
|
||||
self.weight_label.setGeometry(217, 118, 118, 29) # 保持原坐标
|
||||
self.weight_label.setStyleSheet("""
|
||||
background-color: #3a2528;
|
||||
color: #A2EF4D;
|
||||
font-size: 20px;
|
||||
background-color: #143a6e;
|
||||
color: #16ffff;
|
||||
font-size: 18px;
|
||||
""")
|
||||
# 文本在标签内部居中显示
|
||||
self.weight_label.setAlignment(Qt.AlignCenter)
|
||||
self.weight_label.setText("2000kg") # 初始文本
|
||||
|
||||
# 显示管片型号的标签 如 B1
|
||||
self.segment_model_label = QLabel(self)
|
||||
self.segment_model_label.setGeometry(98, 150, 116, 29)
|
||||
|
||||
# 显示管片型号的标签
|
||||
self.segment_model_label = QLabel("中埋: R12", container)
|
||||
self.segment_model_label.setGeometry(98, 150, 116, 29)
|
||||
self.segment_model_label.setStyleSheet("""
|
||||
background-color: #3a2528;
|
||||
color: #A2EF4D;
|
||||
font-size: 20px;
|
||||
background-color: #143a6e;
|
||||
color: #16ffff;
|
||||
font-size: 18px;
|
||||
""")
|
||||
# 文本在标签内部居中显示
|
||||
self.segment_model_label.setAlignment(Qt.AlignCenter)
|
||||
self.segment_model_label.setText("B1") # 初始文本
|
||||
|
||||
# 显示频率的标签,单位是 Hz
|
||||
self.frequency_label = QLabel(self)
|
||||
self.frequency_label.setGeometry(217, 150, 118, 29)
|
||||
|
||||
# 显示频率的标签
|
||||
self.frequency_label = QLabel("210Hz", container)
|
||||
self.frequency_label.setGeometry(217, 150, 118, 29)
|
||||
self.frequency_label.setStyleSheet("""
|
||||
background-color: #3a2528;
|
||||
color: #A2EF4D;
|
||||
font-size: 20px;
|
||||
background-color: #143a6e;
|
||||
color: #16ffff;
|
||||
font-size: 18px;
|
||||
""")
|
||||
# 文本在标签内部居中显示
|
||||
self.frequency_label.setAlignment(Qt.AlignCenter)
|
||||
self.frequency_label.setText("230Hz") # 初始文本
|
||||
|
||||
# 显示管片编号的标签 如 SHRB1-3
|
||||
self.segment_number_label = QLabel(self)
|
||||
self.segment_number_label.setGeometry(338, 150, 116, 29)
|
||||
|
||||
# 显示管片编号的标签
|
||||
self.segment_number_label = QLabel("SHRB1-3", container)
|
||||
self.segment_number_label.setGeometry(338, 150, 116, 29)
|
||||
self.segment_number_label.setStyleSheet("""
|
||||
background-color: #3a2528;
|
||||
color: #A2EF4D;
|
||||
font-size: 20px;
|
||||
background-color: #143a6e;
|
||||
color: #16ffff;
|
||||
font-size: 18px;
|
||||
""")
|
||||
# 文本在标签内部居中显示
|
||||
self.segment_number_label.setAlignment(Qt.AlignCenter)
|
||||
self.segment_number_label.setText("SHRB1-3") # 初始文本
|
||||
|
||||
# 显示状态的标签 (如:振动中等)
|
||||
self.state_label = QLabel(self)
|
||||
self.state_label.setGeometry(217, 182, 118, 29)
|
||||
self.state_label.setStyleSheet("""
|
||||
background-color: #3a2528;
|
||||
color: #A2EF4D;
|
||||
font-size: 20px;
|
||||
|
||||
# 显示管片尺寸的标签
|
||||
self.segment_size_label = QLabel("6900*1500", container)
|
||||
self.segment_size_label.setGeometry(98, 182, 116, 29)
|
||||
self.segment_size_label.setStyleSheet("""
|
||||
background-color: #143a6e;
|
||||
color: #16ffff;
|
||||
font-size: 18px;
|
||||
""")
|
||||
self.segment_size_label.setAlignment(Qt.AlignCenter)
|
||||
|
||||
# 显示状态的标签
|
||||
self.state_label = QLabel("振动中", container)
|
||||
self.state_label.setGeometry(217, 182, 118, 29)
|
||||
self.state_label.setStyleSheet("""
|
||||
background-color: #143a6e;
|
||||
color: #16ffff;
|
||||
font-size: 18px;
|
||||
""")
|
||||
# 文本在标签内部居中显示
|
||||
self.state_label.setAlignment(Qt.AlignCenter)
|
||||
self.state_label.setText("振动中") # 初始文本
|
||||
|
||||
# 4. 将拱形进度条容器添加到主布局
|
||||
main_layout.addWidget(container, alignment=Qt.AlignCenter)
|
||||
|
||||
# 进度条测试
|
||||
def testProgress(self, seconds: float):
|
||||
@ -220,7 +262,7 @@ class ArcProgressWidget(QWidget):
|
||||
def setState(self, stateStr:str):
|
||||
self.state_label.setText(stateStr)
|
||||
|
||||
# 管片类型设置 (B1、B2等)
|
||||
# 管片类型设置 (B1、B2、中埋: R12 等)
|
||||
def setSegmentModel(self, segmentModelStr:str):
|
||||
self.segment_model_label.setText(segmentModelStr)
|
||||
|
||||
@ -234,6 +276,6 @@ if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
window = ArcProgressWidget()
|
||||
window.testProgress(120)
|
||||
window.setState("测试中")
|
||||
# window.setState("测试中")
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
|
||||
291
view/widgets/bottom_control_widget.py
Normal file
291
view/widgets/bottom_control_widget.py
Normal file
@ -0,0 +1,291 @@
|
||||
from PySide6.QtWidgets import (QWidget, QHBoxLayout, QLabel, QPushButton,
|
||||
QMessageBox, QSpacerItem, QSizePolicy)
|
||||
from PySide6.QtGui import QPixmap, QPainter, QFont, QPen
|
||||
from PySide6.QtCore import Qt, QDateTime, QEvent, QSize
|
||||
from view.widgets.switch_button import SwitchButton
|
||||
import resources.resources_rc
|
||||
|
||||
class BottomControlWidget(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.initUI()
|
||||
|
||||
def initUI(self):
|
||||
# 1. 加载背景图并设置控件尺寸
|
||||
bg_path = ":/icons/images/底部背景.png"
|
||||
self.bg_pixmap = QPixmap(bg_path)
|
||||
if self.bg_pixmap.isNull():
|
||||
print("警告:底部背景.png 加载失败,请检查路径!")
|
||||
self.setFixedSize(1280, 66)
|
||||
else:
|
||||
self.setFixedSize(self.bg_pixmap.size())
|
||||
|
||||
# 2. 主布局(水平布局,组件间距6px)
|
||||
main_layout = QHBoxLayout(self)
|
||||
main_layout.setContentsMargins(10, 10, 10, 10) # 适当留白避免贴边
|
||||
main_layout.setSpacing(6)
|
||||
|
||||
# 3. 逐个添加组件
|
||||
## 3.1 系统诊断按钮(带状态图)
|
||||
self.diagnosis_btn = self.createDiagnosisButton("系统诊断",
|
||||
":/icons/images/底部短按钮1.png",
|
||||
":/icons/images/底部短按钮2.png")
|
||||
main_layout.addWidget(self.diagnosis_btn)
|
||||
|
||||
## 3.2 系统状态消息按钮(长按钮)
|
||||
self.status_msg_btn = self.createLongButton("系统状态消息",
|
||||
":/icons/images/底部长按钮1.png",
|
||||
":/icons/images/底部长按钮2.png")
|
||||
main_layout.addWidget(self.status_msg_btn)
|
||||
|
||||
## 3.3 预警消息列表按钮(长按钮)
|
||||
self.warning_list_btn = self.createLongButton("预警消息列表",
|
||||
":/icons/images/底部长按钮1.png",
|
||||
":/icons/images/底部长按钮2.png")
|
||||
main_layout.addWidget(self.warning_list_btn)
|
||||
|
||||
## 3.4 系统中心按钮(短按钮,无状态图)
|
||||
self.center_btn = self.createShortButton("系统中心",
|
||||
":/icons/images/底部短按钮1.png",
|
||||
":/icons/images/底部短按钮2.png")
|
||||
main_layout.addWidget(self.center_btn)
|
||||
|
||||
## 3.5 自动模式(文字+开关)
|
||||
self.auto_mode_widget = self.createAutoModeWidget()
|
||||
main_layout.addWidget(self.auto_mode_widget)
|
||||
|
||||
## 3.6 版本信息标签(可点击弹框)
|
||||
self.version_label = QLabel("V1.0")
|
||||
self.version_label.setStyleSheet("""
|
||||
color: #16ffff;
|
||||
font-size: 20px;
|
||||
text-decoration: underline;
|
||||
""")
|
||||
self.version_label.installEventFilter(self) # 安装事件过滤器
|
||||
main_layout.addWidget(self.version_label, alignment=Qt.AlignRight | Qt.AlignBottom)
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
"""事件过滤器:使用QEvent枚举判断事件类型"""
|
||||
if obj == self.version_label and event.type() == QEvent.MouseButtonPress:
|
||||
self.showVersionInfo()
|
||||
return True # 拦截事件
|
||||
return super().eventFilter(obj, event) # 其他事件正常处理
|
||||
|
||||
def createDiagnosisButton(self, text, normal_img, hover_img):
|
||||
"""创建带状态图的系统诊断按钮"""
|
||||
# 加载短按钮图片获取尺寸
|
||||
normal_pix = QPixmap(normal_img)
|
||||
if normal_pix.isNull():
|
||||
print(f"警告:{normal_img} 加载失败")
|
||||
btn_width, btn_height = 120, 40
|
||||
else:
|
||||
btn_width, btn_height = normal_pix.width(), normal_pix.height()
|
||||
|
||||
btn = QPushButton()
|
||||
btn.setFixedSize(btn_width, btn_height)
|
||||
# 1. 按钮设置文字
|
||||
btn.setText(text)
|
||||
|
||||
# 2. 状态图标(作为按钮的子控件,单独定位)
|
||||
self.status_icon = QLabel(btn)
|
||||
status_pix = QPixmap(":/icons/images/底部系统状态绿.png")
|
||||
if not status_pix.isNull():
|
||||
self.status_icon.setFixedSize(status_pix.width(), status_pix.height())
|
||||
self.status_icon.setPixmap(status_pix)
|
||||
# 图标定位:左侧9px,垂直居中
|
||||
icon_y = (btn_height - self.status_icon.height()) // 2
|
||||
self.status_icon.move(9, icon_y)
|
||||
|
||||
# 3. 设置鼠标手势
|
||||
btn.setCursor(Qt.PointingHandCursor)
|
||||
|
||||
# 4. 按钮样式表
|
||||
btn.setStyleSheet(f"""
|
||||
QPushButton {{
|
||||
background-image: url({normal_img});
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
border: none;
|
||||
color: #3bfff8;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
padding-left: 9px;
|
||||
}}
|
||||
QPushButton:hover {{
|
||||
background-image: url({hover_img});
|
||||
color: #001c83; /* hover状态文字颜色 */
|
||||
}}
|
||||
""")
|
||||
|
||||
return btn
|
||||
|
||||
def setStatusIcon(self, status, count=0):
|
||||
"""切换状态图标并显示数量"""
|
||||
icon_map = {
|
||||
"normal": ":/icons/images/底部系统状态绿.png",
|
||||
"warning": ":/icons/images/底部系统状态黄.png",
|
||||
"error": ":/icons/images/底部系统状态红.png"
|
||||
}
|
||||
pixmap_path = icon_map.get(status, icon_map["normal"])
|
||||
pixmap = QPixmap(pixmap_path)
|
||||
|
||||
if not pixmap.isNull():
|
||||
temp_pix = pixmap.copy() # 复制一份 pixmap 用于绘制
|
||||
|
||||
# 绘制 异常或警告 数量(若count>0)
|
||||
if count > 0 and status != "normal": # 绿色的正常情况,不需要显示数字
|
||||
painter = QPainter(temp_pix) # 在副本上绘制
|
||||
painter.setPen(QPen(Qt.white, 2)) # 白色画笔
|
||||
font = painter.font() # 获取当前字体
|
||||
# 调整字体大小
|
||||
if count > 99:
|
||||
font.setPointSize(10)
|
||||
else:
|
||||
font.setPointSize(14)
|
||||
painter.setFont(font) # 应用字体设置
|
||||
|
||||
if count <= 99: # 两位数字
|
||||
painter.drawText(temp_pix.rect(), Qt.AlignCenter, str(count)) # 居中绘制数字
|
||||
else:
|
||||
painter.drawText(temp_pix.rect(), Qt.AlignCenter, "99+")
|
||||
painter.end() # 手动结束绘制
|
||||
|
||||
# 将绘制完成的副本设置回标签
|
||||
self.status_icon.setPixmap(temp_pix)
|
||||
|
||||
def createShortButton(self, text, normal_img, hover_img):
|
||||
"""创建短按钮"""
|
||||
# 以正常状态图片尺寸为准,确定按钮宽高
|
||||
normal_pix = QPixmap(normal_img)
|
||||
if normal_pix.isNull():
|
||||
print(f"警告:{normal_img} 加载失败")
|
||||
btn_width, btn_height = 155, 50 # 图片加载失败时的默认尺寸
|
||||
else:
|
||||
btn_width, btn_height = normal_pix.width(), normal_pix.height() # 与背景图尺寸一致
|
||||
|
||||
# 1. 创建按钮并设置基础属性
|
||||
btn = QPushButton()
|
||||
btn.setFixedSize(btn_width, btn_height) # 按钮尺寸匹配背景图
|
||||
btn.setText(text) # 用按钮自带文字,替代QLabel
|
||||
|
||||
# 2. 设置鼠标手势
|
||||
btn.setCursor(Qt.PointingHandCursor)
|
||||
|
||||
# 3. 样式表:控制文字(16px+居中+颜色)、背景图、hover效果
|
||||
btn.setStyleSheet(f"""
|
||||
QPushButton {{
|
||||
/* 背景图设置 */
|
||||
background-image: url({normal_img});
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
border: none;
|
||||
font-size: 20px;
|
||||
color: #3bfff8;
|
||||
text-align: center;
|
||||
}}
|
||||
QPushButton:hover {{
|
||||
/* hover时切换背景图 */
|
||||
background-image: url({hover_img});
|
||||
/* hover时文字变色 */
|
||||
color: #001c83;
|
||||
}}
|
||||
""")
|
||||
|
||||
return btn
|
||||
|
||||
def createLongButton(self, text, normal_img, hover_img):
|
||||
"""创建长按钮"""
|
||||
# 以正常状态图片尺寸为准,确定按钮宽高
|
||||
normal_pix = QPixmap(normal_img)
|
||||
if normal_pix.isNull():
|
||||
print(f"警告:{normal_img} 加载失败")
|
||||
btn_width, btn_height = 339, 50 # 图片加载失败时的默认尺寸
|
||||
else:
|
||||
btn_width, btn_height = normal_pix.width(), normal_pix.height()
|
||||
|
||||
# 1. 创建按钮并设置基础属性
|
||||
btn = QPushButton()
|
||||
btn.setFixedSize(btn_width, btn_height) # 按钮尺寸匹配背景图
|
||||
btn.setText(text)
|
||||
|
||||
# 2. 设置鼠标手势
|
||||
btn.setCursor(Qt.PointingHandCursor)
|
||||
|
||||
# 3. 样式表:控制文字(20px字体+居中+颜色)、背景图、hover效果
|
||||
btn.setStyleSheet(f"""
|
||||
QPushButton {{
|
||||
background-image: url({normal_img});
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
border: none;
|
||||
font-size: 20px;
|
||||
color: #3bfff8;
|
||||
text-align: center;
|
||||
}}
|
||||
QPushButton:hover {{
|
||||
background-image: url({hover_img});
|
||||
color: #001c83;
|
||||
}}
|
||||
""")
|
||||
|
||||
return btn
|
||||
|
||||
def createAutoModeWidget(self):
|
||||
"""创建自动模式组件"""
|
||||
bg_img = ":/icons/images/底部短按钮1.png"
|
||||
bg_pix = QPixmap(bg_img)
|
||||
if bg_pix.isNull():
|
||||
print(f"警告:{bg_img} 加载失败")
|
||||
widget_width, widget_height = 150, 40
|
||||
else:
|
||||
widget_width, widget_height = bg_pix.width(), bg_pix.height()
|
||||
|
||||
widget = QWidget()
|
||||
widget.setFixedSize(widget_width, widget_height)
|
||||
widget.setObjectName("autoModeWidget")
|
||||
widget.setStyleSheet(f"""
|
||||
QWidget #autoModeWidget{{
|
||||
background-image: url({bg_img});
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
border: none;
|
||||
}}
|
||||
""")
|
||||
|
||||
layout = QHBoxLayout(widget)
|
||||
layout.setContentsMargins(10, 0, 10, 0)
|
||||
layout.setSpacing(10)
|
||||
|
||||
# 文字
|
||||
text_label = QLabel("自动模式")
|
||||
text_label.setStyleSheet("color: #3bfff8; font-size: 20px;")
|
||||
layout.addWidget(text_label, alignment=Qt.AlignVCenter)
|
||||
|
||||
# 开关
|
||||
self.auto_switch = SwitchButton()
|
||||
layout.addWidget(self.auto_switch, alignment=Qt.AlignVCenter | Qt.AlignRight)
|
||||
|
||||
return widget
|
||||
|
||||
def showVersionInfo(self):
|
||||
"""显示版本信息弹窗"""
|
||||
info = "智能浇筑系统\n版本:V1.0\n"
|
||||
QMessageBox.information(self, "版本信息", info)
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""绘制背景图"""
|
||||
super().paintEvent(event)
|
||||
if not self.bg_pixmap.isNull():
|
||||
painter = QPainter(self)
|
||||
painter.drawPixmap(0, 0, self.bg_pixmap)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
widget = BottomControlWidget()
|
||||
widget.setStatusIcon("error", 33)
|
||||
widget.show()
|
||||
sys.exit(app.exec())
|
||||
|
||||
@ -1,36 +1,62 @@
|
||||
from PySide6.QtWidgets import QPushButton
|
||||
from PySide6.QtGui import QPainter, QColor, QFont, Qt, QBrush
|
||||
|
||||
from PySide6.QtWidgets import QPushButton
|
||||
from PySide6.QtGui import QPainter, QFont, Qt, QPixmap, QColor
|
||||
from PySide6.QtCore import QRectF
|
||||
import resources.resources_rc
|
||||
|
||||
# 用于 开、拱 等功能按钮
|
||||
class CircularButton(QPushButton):
|
||||
"""自定义圆形按钮,处理 hover 颜色变化"""
|
||||
"""自定义圆形按钮"""
|
||||
def __init__(self, text, parent=None):
|
||||
super().__init__(text, parent)
|
||||
self.setFixedSize(40, 40)
|
||||
# self.normal_color = QColor(1, 18, 98) # 正常颜色 (RGB: 1,18,98)
|
||||
self.normal_color = QColor(86, 119, 252)
|
||||
self.hover_color = QColor(24, 253, 255) # hover 颜色 (RGB: 24,253,255)
|
||||
self.current_color = self.normal_color
|
||||
# 设置按钮大小为41x41(与图片尺寸一致)
|
||||
self.setFixedSize(41, 41)
|
||||
|
||||
# 加载背景图片
|
||||
self.normal_pixmap = QPixmap(":/icons/images/圆形按钮背景1.png") # 正常状态图片 需要修改为实际的路径
|
||||
self.hover_pixmap = QPixmap(":/icons/images/圆形按钮背景2.png") # hover状态图片 需要修改为实际的路径
|
||||
|
||||
# 确保图片加载成功(可选:添加错误处理)
|
||||
if self.normal_pixmap.isNull():
|
||||
print("警告:圆形按钮背景1.png 加载失败")
|
||||
if self.hover_pixmap.isNull():
|
||||
print("警告:圆形按钮背景2.png 加载失败")
|
||||
|
||||
# 字体初始化状态和颜色
|
||||
self.is_hover = False
|
||||
self.normal_text_color = QColor(6, 224, 239)
|
||||
self.hover_text_color = QColor(3, 20, 100)
|
||||
|
||||
# 去除默认样式
|
||||
self.setStyleSheet("background: transparent; border: none;")
|
||||
|
||||
def enterEvent(self, event):
|
||||
self.current_color = self.hover_color
|
||||
self.update()
|
||||
self.is_hover = True
|
||||
self.update() # 触发重绘
|
||||
super().enterEvent(event)
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self.current_color = self.normal_color
|
||||
self.update()
|
||||
self.is_hover = False
|
||||
self.update() # 触发重绘
|
||||
super().leaveEvent(event)
|
||||
|
||||
def paintEvent(self, event):
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.Antialiasing)
|
||||
# 绘制圆形背景
|
||||
painter.setBrush(QBrush(self.current_color))
|
||||
painter.setPen(Qt.NoPen)
|
||||
painter.drawEllipse(0, 0, self.width(), self.height())
|
||||
# 绘制白色文字
|
||||
painter.setPen(Qt.white)
|
||||
painter.setFont(QFont("Arial", 10, QFont.Bold))
|
||||
painter.drawText(self.rect(), Qt.AlignCenter, self.text())
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing) # 抗锯齿
|
||||
|
||||
# 绘制对应状态的背景图片
|
||||
current_pixmap = self.hover_pixmap if self.is_hover else self.normal_pixmap
|
||||
if not current_pixmap.isNull():
|
||||
# 绘制图片并适应按钮大小(保持圆形)
|
||||
painter.drawPixmap(self.rect(), current_pixmap)
|
||||
|
||||
# 绘制文字
|
||||
# painter.setPen(self.hover_text_color if self.is_hover else self.normal_text_color)
|
||||
# print("self.is_hover: ", self.is_hover)
|
||||
if self.is_hover: # 根据鼠标是否解除修改字体颜色
|
||||
painter.setPen(self.hover_text_color)
|
||||
else:
|
||||
painter.setPen( self.normal_text_color)
|
||||
|
||||
painter.setFont(QFont("Arial", 12, QFont.Weight.Bold))
|
||||
# 在按钮中心绘制文字
|
||||
painter.drawText(self.rect(), Qt.AlignmentFlag.AlignCenter, self.text())
|
||||
176
view/widgets/clamp_widget.py
Normal file
176
view/widgets/clamp_widget.py
Normal file
@ -0,0 +1,176 @@
|
||||
from PySide6.QtWidgets import QWidget, QApplication
|
||||
from PySide6.QtGui import QPainter, QPen, QBrush, QColor, QTransform
|
||||
from PySide6.QtCore import Qt, QPropertyAnimation, Property, QPointF
|
||||
import sys
|
||||
|
||||
# 料斗夹爪部分,包括夹爪开合动画
|
||||
class ClampWidget(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._angle = 0.0 # 开合角度(0-70度,单个夹具旋转角度为angle/2)
|
||||
self.setFixedSize(176, 127) # 绘图区域大小 240x150
|
||||
# self.rot_center = QPointF(120, 30) # 旋转中心
|
||||
# 重要,调整这里,设置夹爪绘制位置
|
||||
self.rot_center = QPointF(88, 14) # 调整!!! 在主界面中位置需要调整这里
|
||||
self.red_width = 37 # 红框宽度
|
||||
self.red_height = 81 # 红框高度
|
||||
|
||||
def paintEvent(self, event):
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.Antialiasing)
|
||||
|
||||
# ---- 左红框及蓝色部分 ----
|
||||
# 左红框原始顶点(严格对称,以旋转中心为轴)
|
||||
left_red_vertices = [
|
||||
QPointF(self.rot_center.x() - self.red_width, self.rot_center.y()), # 顶部左
|
||||
QPointF(self.rot_center.x(), self.rot_center.y()), # 顶部右(旋转中心)
|
||||
QPointF(self.rot_center.x(), self.rot_center.y() + self.red_height), # 底部右
|
||||
QPointF(self.rot_center.x() - self.red_width, self.rot_center.y() + self.red_height) # 底部左
|
||||
]
|
||||
# 左红框旋转变换(顺时针转angle/2度)
|
||||
painter.save()
|
||||
transform = QTransform()
|
||||
transform.translate(self.rot_center.x(), self.rot_center.y())
|
||||
transform.rotate(self._angle / 2)
|
||||
transform.translate(-self.rot_center.x(), -self.rot_center.y())
|
||||
transformed_left_red = [transform.map(p) for p in left_red_vertices]
|
||||
# 绘制左红框辅助线
|
||||
# painter.setPen(QPen(QColor(255, 0, 0), 1, Qt.DashLine))
|
||||
# painter.setBrush(Qt.NoBrush)
|
||||
# painter.drawPolygon(transformed_left_red)
|
||||
painter.restore()
|
||||
|
||||
# 绘制左蓝色部分 86, 180, 239
|
||||
# painter.setPen(QPen(QColor(0, 120, 255), 2))
|
||||
# painter.setBrush(QBrush(QColor(0, 170, 255, 180)))
|
||||
painter.setPen(QPen(QColor(66, 152, 215, 190), 2))
|
||||
painter.setBrush(QBrush(QColor(86, 180, 239)))
|
||||
# 蓝色顶点:顶部中间17px直线 + 红框内边界
|
||||
top_mid = QPointF(
|
||||
transformed_left_red[0].x() + self.red_width/2 - 10,
|
||||
transformed_left_red[0].y()
|
||||
)
|
||||
top_end = QPointF(top_mid.x() + 17, top_mid.y())
|
||||
bottom_left = transformed_left_red[3]
|
||||
|
||||
# ============================== 特殊计算 bottom_right1 和 bottom_right2 ======
|
||||
# 定义端点
|
||||
A = transformed_left_red[1] # 左红框顶部右顶点
|
||||
B = transformed_left_red[2] # 左红框底部右顶点
|
||||
t = 0.7 # 70%位置
|
||||
|
||||
# 线性插值计算bottom_left2
|
||||
bottom_right2_x = A.x() + t * (B.x() - A.x())
|
||||
bottom_right2_y = A.y() + t * (B.y() - A.y())
|
||||
|
||||
bottom_right1 = QPointF(bottom_right2_x - 5, bottom_right2_y - 8) # 基于bottom_right2 计算坐标
|
||||
bottom_right2 = QPointF(bottom_right2_x, bottom_right2_y)
|
||||
# ===========================================================================
|
||||
|
||||
bottom_right3 = QPointF(transformed_left_red[2].x(), transformed_left_red[2].y())
|
||||
left_blue_vertices = [
|
||||
top_mid,
|
||||
top_end,
|
||||
bottom_right1,
|
||||
bottom_right2,
|
||||
bottom_right3,
|
||||
bottom_left,
|
||||
top_mid
|
||||
]
|
||||
painter.drawPolygon(left_blue_vertices)
|
||||
|
||||
|
||||
# ---- 右红框及蓝色部分 ----
|
||||
# 右红框原始顶点(与左红框完全对称)
|
||||
right_red_vertices = [
|
||||
QPointF(self.rot_center.x(), self.rot_center.y()), # 顶部左(旋转中心)
|
||||
QPointF(self.rot_center.x() + self.red_width, self.rot_center.y()), # 顶部右
|
||||
QPointF(self.rot_center.x() + self.red_width, self.rot_center.y() + self.red_height), # 底部右
|
||||
QPointF(self.rot_center.x(), self.rot_center.y() + self.red_height) # 底部左
|
||||
]
|
||||
# 右红框旋转变换(逆时针转angle/2度)
|
||||
painter.save()
|
||||
transform = QTransform()
|
||||
transform.translate(self.rot_center.x(), self.rot_center.y())
|
||||
transform.rotate(-self._angle / 2)
|
||||
transform.translate(-self.rot_center.x(), -self.rot_center.y())
|
||||
transformed_right_red = [transform.map(p) for p in right_red_vertices]
|
||||
# 绘制右红框辅助线
|
||||
# painter.setPen(QPen(QColor(255, 0, 0), 1, Qt.DashLine))
|
||||
# painter.setBrush(Qt.NoBrush)
|
||||
# painter.drawPolygon(transformed_right_red)
|
||||
painter.restore()
|
||||
|
||||
# 绘制右蓝色部分 83, 175, 234 66, 152, 215
|
||||
painter.setPen(QPen(QColor(66, 152, 215, 190), 2))
|
||||
painter.setBrush(QBrush(QColor(86, 180, 239)))
|
||||
# painter.setPen(QPen(QColor(0, 120, 255), 2))
|
||||
# painter.setBrush(QBrush(QColor(0, 170, 255, 180)))
|
||||
|
||||
# 蓝色顶点:顶部中间17px直线 + 红框内边界
|
||||
top_mid = QPointF(
|
||||
transformed_right_red[1].x() - self.red_width/2 + 10,
|
||||
transformed_right_red[1].y()
|
||||
)
|
||||
top_end = QPointF(top_mid.x() - 17, top_mid.y())
|
||||
|
||||
# ============================== 特殊计算 bottom_left1 和 bottom_left2 =======
|
||||
# 定义端点
|
||||
A = transformed_right_red[0] # 右红框顶部左顶点
|
||||
B = transformed_right_red[3] # 右红框底部左顶点
|
||||
t = 0.7 # 70%位置
|
||||
|
||||
# 线性插值计算bottom_left2
|
||||
bottom_left2_x = A.x() + t * (B.x() - A.x())
|
||||
bottom_left2_y = A.y() + t * (B.y() - A.y())
|
||||
|
||||
bottom_left1 = QPointF(bottom_left2_x + 5, bottom_left2_y - 8) # 基于bottom_left2 计算坐标
|
||||
bottom_left2 = QPointF(bottom_left2_x, bottom_left2_y)
|
||||
# ===========================================================================
|
||||
|
||||
bottom_left3 = transformed_right_red[3]
|
||||
bottom_right = transformed_right_red[2]
|
||||
right_blue_vertices = [
|
||||
top_mid,
|
||||
top_end,
|
||||
bottom_left1,
|
||||
bottom_left2,
|
||||
bottom_left3,
|
||||
bottom_right,
|
||||
top_mid
|
||||
]
|
||||
painter.drawPolygon(right_blue_vertices)
|
||||
|
||||
# ---- 角度属性(用于动画)----
|
||||
def get_angle(self):
|
||||
return self._angle
|
||||
|
||||
def set_angle(self, angle):
|
||||
# 动画夹爪打开,限制范围为 0 到 70度
|
||||
# 注意:为了动画显示效果,就算超过70度,也只按70度计算
|
||||
self._angle = max(0.0, min(70.0, angle))
|
||||
self.update()
|
||||
|
||||
angle = Property(float, get_angle, set_angle)
|
||||
|
||||
# 测试动画
|
||||
def testAnimation(self, target_angle, duration=6):
|
||||
self.animation = QPropertyAnimation(self, b"angle")
|
||||
self.animation.setDuration(duration * 1000) # duration单位为秒
|
||||
self.animation.setStartValue(self._angle)
|
||||
self.animation.setEndValue(target_angle)
|
||||
self.animation.start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
widget = ClampWidget()
|
||||
|
||||
widget.show()
|
||||
# widget.testAnimation(70)
|
||||
close_anim = QPropertyAnimation(widget, b"angle")
|
||||
close_anim.setDuration(6000)
|
||||
close_anim.setStartValue(0)
|
||||
close_anim.setEndValue(60)
|
||||
close_anim.start()
|
||||
# widget.set_angle(30)
|
||||
sys.exit(app.exec())
|
||||
214
view/widgets/conveyor_system_widget.py
Normal file
214
view/widgets/conveyor_system_widget.py
Normal file
@ -0,0 +1,214 @@
|
||||
import sys
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
)
|
||||
from PySide6.QtGui import QPixmap
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
import resources.resources_rc
|
||||
|
||||
# 传送系统控件 (包括传送带 和 料斗)
|
||||
class ConveyorSystemWidget(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("料斗与传送带界面")
|
||||
self.setFixedSize(443, 190)
|
||||
self.init_ui()
|
||||
self._bind()
|
||||
|
||||
def init_ui(self):
|
||||
# 主布局
|
||||
main_layout = QVBoxLayout(self)
|
||||
main_layout.setSpacing(0) # 组件间间距
|
||||
# main_layout.setContentsMargins(20, 20, 20, 20)
|
||||
main_layout.setContentsMargins(10, 120, 6, 3)
|
||||
|
||||
# 添加料斗组件
|
||||
self.hopper_widget = self.create_upper_hopper()
|
||||
# main_layout.addWidget(self.hopper_widget)
|
||||
self.hopper_widget.setParent(self) # 明确父对象为窗口,确保显示在窗口上
|
||||
self.hopper_widget.move(190, 7) # 中间的过渡位置
|
||||
self.hopper_widget.setHidden(True)
|
||||
|
||||
# 添加传送带组件
|
||||
self.conveyor_widget = self.create_conveyor()
|
||||
main_layout.addWidget(self.conveyor_widget, alignment=Qt.AlignLeft)
|
||||
|
||||
# 添加传送带控制按钮
|
||||
self.btn_layout = self.create_conveyor_buttons()
|
||||
main_layout.addLayout(self.btn_layout)
|
||||
|
||||
def create_upper_hopper(self):
|
||||
"""创建简化版上位料斗(移除了按钮、文字标签等元素)"""
|
||||
group = QWidget()
|
||||
layout = QVBoxLayout(group)
|
||||
layout.setSpacing(0)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# 外框图片
|
||||
outer_img = ":/icons/images/料斗1.png"
|
||||
outer_pixmap = QPixmap(outer_img)
|
||||
if outer_pixmap.isNull():
|
||||
print(f"警告:图片 {outer_img} 加载失败,请检查路径!")
|
||||
return group
|
||||
|
||||
group.setFixedSize(outer_pixmap.size()) # 设置尺寸, 大小和外框一样
|
||||
|
||||
# 背景容器
|
||||
upper_bg_widget = QWidget()
|
||||
upper_bg_widget.setFixedSize(outer_pixmap.width(), outer_pixmap.height())
|
||||
upper_bg_widget.setStyleSheet(
|
||||
f"background-image: url({outer_img}); "
|
||||
"background-repeat: no-repeat; "
|
||||
"background-position: center;"
|
||||
)
|
||||
layout.addWidget(upper_bg_widget, alignment=Qt.AlignCenter)
|
||||
|
||||
# 内框图片
|
||||
inner_img = ":/icons/images/料斗2.png"
|
||||
inner_pixmap = QPixmap(inner_img)
|
||||
if not inner_pixmap.isNull():
|
||||
upper_inner_label = QLabel(upper_bg_widget)
|
||||
upper_inner_label.setPixmap(inner_pixmap)
|
||||
upper_inner_label.setFixedSize(inner_pixmap.width(), inner_pixmap.height())
|
||||
upper_inner_label.move(14, 9) # 保持原位置
|
||||
|
||||
return group
|
||||
|
||||
def create_conveyor(self):
|
||||
"""创建传送带组件(包含左右齿轮,group容器背景为传送带图片)"""
|
||||
group = QWidget()
|
||||
group.setObjectName("conveyorGroup")
|
||||
|
||||
# 1. 加载传送带图片,设置group的尺寸和背景
|
||||
conveyor_path = ":/icons/images/传送带.png" # 需要替换为实际的路径
|
||||
conveyor_pix = QPixmap(conveyor_path)
|
||||
if conveyor_pix.isNull():
|
||||
print("警告:传送带图片加载失败!请检查图片路径是否正确!")
|
||||
else:
|
||||
# group的尺寸 = 传送带图片尺寸(保证背景图完整显示)
|
||||
group.setFixedSize(conveyor_pix.size())
|
||||
group.setStyleSheet(f"""
|
||||
#conveyorGroup {{
|
||||
background-image: url({conveyor_path});
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}}
|
||||
""")
|
||||
|
||||
# 2. 给group设置布局(用于放置左右齿轮)
|
||||
layout = QHBoxLayout(group)
|
||||
layout.setSpacing(0) # 齿轮与容器边缘无间距
|
||||
# 内边距(根据实际图片调整)
|
||||
layout.setContentsMargins(3, 3, 2, 4)
|
||||
|
||||
# 3. 左侧齿轮(直接放在group的布局里,层级在背景之上)
|
||||
left_gear = QLabel(group)
|
||||
left_gear_pix = QPixmap(":/icons/images/传送带齿轮.png")
|
||||
if left_gear_pix.isNull():
|
||||
print("警告:左侧齿轮图片加载失败!")
|
||||
else:
|
||||
left_gear.setPixmap(left_gear_pix)
|
||||
left_gear.setFixedSize(left_gear_pix.size())
|
||||
# 左对齐,让齿轮靠在传送带背景的左端
|
||||
layout.addWidget(left_gear, alignment=Qt.AlignLeft | Qt.AlignVCenter)
|
||||
|
||||
# 4. 右侧齿轮(通过addStretch推到最右边)
|
||||
right_gear = QLabel(group)
|
||||
right_gear_pix = QPixmap(":/icons/images/传送带齿轮.png")
|
||||
if right_gear_pix.isNull():
|
||||
print("警告:右侧齿轮图片加载失败!")
|
||||
else:
|
||||
right_gear.setPixmap(right_gear_pix)
|
||||
right_gear.setFixedSize(right_gear_pix.size())
|
||||
# 右对齐,让齿轮靠在传送带背景的右端
|
||||
layout.addWidget(right_gear, alignment=Qt.AlignRight | Qt.AlignVCenter)
|
||||
|
||||
return group
|
||||
|
||||
def create_conveyor_buttons(self):
|
||||
"""创建传送带控制按钮(左右箭头按钮)"""
|
||||
layout = QHBoxLayout()
|
||||
layout.setSpacing(0) # 两个按钮之间的间距
|
||||
|
||||
# 左侧按钮
|
||||
self.left_btn = QPushButton()
|
||||
self.left_btn.setFixedSize(25, 25)
|
||||
self.left_btn.setStyleSheet(
|
||||
"""
|
||||
QPushButton {
|
||||
background-image: url(:/icons/images/传送带箭头按钮左1.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
border: none;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-image: url(:/icons/images/传送带箭头按钮左2.png);
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-image: url(:/icons/images/传送带箭头按钮左2.png);
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
# 右侧按钮
|
||||
self.right_btn = QPushButton()
|
||||
self.right_btn.setFixedSize(25, 25)
|
||||
self.right_btn.setStyleSheet(
|
||||
"""
|
||||
QPushButton {
|
||||
background-image: url(:/icons/images/传送带箭头按钮右1.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
border: none;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-image: url(:/icons/images/传送带箭头按钮右2.png);
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-image: url(:/icons/images/传送带箭头按钮右2.png);
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
# addStretch调整按钮的位置
|
||||
layout.addStretch(1)
|
||||
layout.addWidget(self.left_btn, alignment=Qt.AlignLeft)
|
||||
layout.addStretch(3)
|
||||
layout.addWidget(self.right_btn, alignment=Qt.AlignLeft)
|
||||
layout.addStretch(4)
|
||||
return layout
|
||||
|
||||
def _bind(self):
|
||||
# self.left_btn.clicked.connect(self.moveHopperBelowMixer)
|
||||
# self.right_btn.clicked.connect(self.moveHopperToTransition)
|
||||
pass
|
||||
|
||||
# 移动料斗到搅拌机下方 (传送带中间位置)
|
||||
def moveHopperBelowMixer(self):
|
||||
self.hopper_widget.move(34, 7) # 搅拌机下方坐标
|
||||
|
||||
# 移动料斗到中间过渡的位置
|
||||
def moveHopperToTransition(self):
|
||||
"""将料斗移动到中间过渡位置(用于位置切换过程)"""
|
||||
self.hopper_widget.move(190, 7) # 中间过渡坐标
|
||||
|
||||
# 隐藏料斗 (用于传送带中 料斗向右移动完成之后)
|
||||
def hideHopper(self):
|
||||
self.hopper_widget.setHidden(True)
|
||||
|
||||
# 显示料斗 (用于传送带中 料斗开始向左移动时)
|
||||
def showHopper(self):
|
||||
self.hopper_widget.setHidden(False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
window = ConveyorSystemWidget()
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
@ -7,7 +7,9 @@ import sys
|
||||
# 圆形按钮
|
||||
from .circular_button import CircularButton
|
||||
|
||||
import resources.resource_rc
|
||||
from .clamp_widget import ClampWidget
|
||||
|
||||
import resources.resources_rc
|
||||
|
||||
class HopperWidget(QWidget):
|
||||
# 上料斗破拱信号
|
||||
@ -28,7 +30,8 @@ class HopperWidget(QWidget):
|
||||
|
||||
# 料斗控制界面的固定大小为 332x482,
|
||||
# 需要根据具体的料斗的图片来调整
|
||||
self.setFixedSize(356, 496)
|
||||
# self.setFixedSize(356, 496)
|
||||
self.setFixedSize(356, 516)
|
||||
|
||||
# 创建上位和下位料斗
|
||||
self.upper_hopper = self.create_upper_hopper()
|
||||
@ -49,7 +52,8 @@ class HopperWidget(QWidget):
|
||||
|
||||
# 上位的 料斗控件的固定尺寸 !!!!
|
||||
# 注意: 这里需要根据具体的料斗图片来修改
|
||||
group.setFixedSize(332, 202)
|
||||
# group.setFixedSize(332, 202)
|
||||
group.setFixedSize(332, 222) # 调整!!
|
||||
# group.setStyleSheet("background-color: green")
|
||||
|
||||
layout = QVBoxLayout(group)
|
||||
@ -58,13 +62,22 @@ class HopperWidget(QWidget):
|
||||
|
||||
# 标题标签(上位)
|
||||
self.upper_title_label = QLabel("上位料斗")
|
||||
self.upper_title_label.setFixedSize(79, 17)
|
||||
self.upper_title_label.setFixedSize(117, 23)
|
||||
self.upper_title_label.setAlignment(Qt.AlignCenter)
|
||||
self.upper_title_label.setStyleSheet("color: #2fd3f2; font-size: 14px; font-weight: bold;")
|
||||
# self.upper_title_label.setStyleSheet("color: #2fd3f2; font-size: 14px; font-weight: bold;")
|
||||
# self.upper_title_label.setStyleSheet("color: #0bffff; font-size: 18px;")
|
||||
self.upper_title_label.setStyleSheet("""
|
||||
color: #0bffff;
|
||||
font-size: 18px;
|
||||
background-image: url(:/icons/images/文字标题底.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
margin-bottom: 4px;
|
||||
""")
|
||||
layout.addWidget(self.upper_title_label, alignment=Qt.AlignCenter)
|
||||
|
||||
# 加载外框图片
|
||||
outer_img = ":/icons/images/上位料斗1.png"
|
||||
outer_img = ":/icons/images/料斗1.png"
|
||||
outer_pixmap = QPixmap(outer_img)
|
||||
if outer_pixmap.isNull():
|
||||
print(f"警告:图片 {outer_img} 加载失败,请检查路径!")
|
||||
@ -77,9 +90,10 @@ class HopperWidget(QWidget):
|
||||
self.upper_bg_widget.setFixedSize(outer_width, outer_height)
|
||||
self.upper_bg_widget.setStyleSheet(f"background-image: url({outer_img}); background-repeat: no-repeat; background-position: center;")
|
||||
layout.addWidget(self.upper_bg_widget, alignment=Qt.AlignCenter)
|
||||
|
||||
|
||||
# 内框图片(上位)
|
||||
inner_img = ":/icons/images/上位料斗2.png"
|
||||
inner_img = ":/icons/images/料斗2.png"
|
||||
inner_pixmap = QPixmap(inner_img)
|
||||
if not inner_pixmap.isNull():
|
||||
self.upper_inner_label = QLabel(self.upper_bg_widget)
|
||||
@ -91,10 +105,10 @@ class HopperWidget(QWidget):
|
||||
status_img = ":/icons/images/料斗状态绿.png"
|
||||
status_pixmap = QPixmap(status_img)
|
||||
if not status_pixmap.isNull():
|
||||
status_pixmap = status_pixmap.scaled(22, 22, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||
status_pixmap = status_pixmap.scaled(13, 13, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||
self.upper_status_label = QLabel(self.upper_bg_widget)
|
||||
self.upper_status_label.setPixmap(status_pixmap)
|
||||
self.upper_status_label.move(32, 18)
|
||||
self.upper_status_label.move(22, 12)
|
||||
self.upper_status_label.setScaledContents(False)
|
||||
self.upper_status_label.setStyleSheet("background: none;")
|
||||
|
||||
@ -102,29 +116,31 @@ class HopperWidget(QWidget):
|
||||
arch_img = ":/icons/images/破拱.png"
|
||||
arch_pixmap = QPixmap(arch_img)
|
||||
if not arch_pixmap.isNull():
|
||||
arch_pixmap = arch_pixmap.scaled(24, 21, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||
arch_pixmap = arch_pixmap.scaled(16, 13, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||
self.upper_arch_label = QLabel(self.upper_bg_widget)
|
||||
self.upper_arch_label.setPixmap(arch_pixmap)
|
||||
self.upper_arch_label.move(outer_width - 56, 18)
|
||||
self.upper_arch_label.move(outer_width - 39, 12)
|
||||
self.upper_arch_label.setStyleSheet("background: none;")
|
||||
self.upper_arch_label.setHidden(True) # 初始,不破拱状态,隐藏
|
||||
|
||||
# 重量文字(上位)
|
||||
self.upper_weight_label = QLabel("5000kg", self.upper_bg_widget)
|
||||
self.upper_weight_label.setAlignment(Qt.AlignCenter)
|
||||
self.upper_weight_label.setStyleSheet("background: none; background-color: #262c38; color: #79c053; font-size: 14px; font-weight: bold;")
|
||||
self.upper_weight_label.setFixedSize(93, 22)
|
||||
self.upper_weight_label.move(outer_width//2 - 46, outer_height//2 - 30)
|
||||
self.upper_weight_label.setStyleSheet("background: none; background-color: #003669; color: #16ffff; font-size: 18px;")
|
||||
# self.upper_weight_label.setFixedSize(93, 22)
|
||||
self.upper_weight_label.setFixedSize(120, 29)
|
||||
self.upper_weight_label.move(outer_width//2 - 60, outer_height//2 - 46)
|
||||
|
||||
# 额外文字(上位)
|
||||
self.upper_extra_label = QLabel("2.0方(预估)", self.upper_bg_widget)
|
||||
self.upper_extra_label.setAlignment(Qt.AlignCenter)
|
||||
self.upper_extra_label.setStyleSheet("background: none; background-color: #262c38; color: #79c053; font-size: 14px; font-weight: bold;")
|
||||
self.upper_extra_label.setFixedSize(93, 22)
|
||||
self.upper_extra_label.move(outer_width//2 - 46, outer_height//2)
|
||||
# #262c38 #16ffff #131427 #003669
|
||||
self.upper_extra_label.setStyleSheet("background: none; background-color: #003669; color: #16ffff; font-size: 18px;")
|
||||
self.upper_extra_label.setFixedSize(120, 29)
|
||||
self.upper_extra_label.move(outer_width//2 - 60, outer_height//2 - 13)
|
||||
|
||||
# 料斗夹具图片(上位)
|
||||
# clamp_img = ":/icons/images/料斗夹具.png"
|
||||
# clamp_img = "料斗夹具.png"
|
||||
# clamp_pixmap = QPixmap(clamp_img)
|
||||
# if not clamp_pixmap.isNull():
|
||||
# self.upper_clamp_label = QLabel()
|
||||
@ -134,29 +150,35 @@ class HopperWidget(QWidget):
|
||||
|
||||
# 料斗夹具图片(上位,固定152x60尺寸,图片自适应缩放)
|
||||
# 注意:目前由于给出的 料斗夹具图片尺寸大了,只能够先缩放,后期图片对了,可以用上面的
|
||||
clamp_img = ":/icons/images/料斗夹具.png"
|
||||
clamp_pixmap = QPixmap(clamp_img)
|
||||
if not clamp_pixmap.isNull():
|
||||
self.upper_clamp_label = QLabel()
|
||||
# 1. 固定QLabel尺寸为152x60
|
||||
self.upper_clamp_label.setFixedSize(152, 60)
|
||||
# 2. 图片缩放到152x60(保持比例,平滑缩放)
|
||||
scaled_pixmap = clamp_pixmap.scaled(
|
||||
152, 60,
|
||||
Qt.KeepAspectRatio, # 保持图片原始比例,避免变形
|
||||
Qt.SmoothTransformation # 平滑缩放,提升显示效果
|
||||
)
|
||||
self.upper_clamp_label.setPixmap(scaled_pixmap)
|
||||
# 3. 确保图片在QLabel中居中显示
|
||||
self.upper_clamp_label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(self.upper_clamp_label, alignment=Qt.AlignCenter)
|
||||
# clamp_img = "料斗夹具.png"
|
||||
# clamp_pixmap = QPixmap(clamp_img)
|
||||
# if not clamp_pixmap.isNull():
|
||||
# self.upper_clamp_label = QLabel()
|
||||
# # 1. 固定QLabel尺寸为152x60 51,84
|
||||
# self.upper_clamp_label.setFixedSize(152, 60)
|
||||
# # 2. 图片缩放到152x60(保持比例,平滑缩放)
|
||||
# scaled_pixmap = clamp_pixmap.scaled(
|
||||
# 152, 60,
|
||||
# Qt.KeepAspectRatio, # 保持图片原始比例,避免变形
|
||||
# Qt.SmoothTransformation # 平滑缩放,提升显示效果
|
||||
# )
|
||||
# self.upper_clamp_label.setPixmap(scaled_pixmap)
|
||||
# # 3. 确保图片在QLabel中居中显示
|
||||
# self.upper_clamp_label.setAlignment(Qt.AlignCenter)
|
||||
# layout.addWidget(self.upper_clamp_label, alignment=Qt.AlignCenter)
|
||||
|
||||
# 按钮(上位)
|
||||
# 夹具容器
|
||||
self.upper_clamp_widget = ClampWidget()
|
||||
layout.addWidget(self.upper_clamp_widget, alignment=Qt.AlignCenter)
|
||||
self.upper_bg_widget.raise_() # 夹具图层位于 upper_bg_widget之下
|
||||
|
||||
# 按钮(上位料斗)
|
||||
self.upper_open_btn = CircularButton("开", group)
|
||||
self.upper_open_btn.move(60, 153) # 上位料斗的开按钮位置
|
||||
# self.upper_open_btn.move(60, 153) # 上位料斗的开按钮位置
|
||||
self.upper_open_btn.move(290, 90)
|
||||
|
||||
self.upper_close_btn = CircularButton("关", group)
|
||||
self.upper_close_btn.move(233, 153) # 上位料斗的关按钮位置
|
||||
# self.upper_close_btn = CircularButton("关", group)
|
||||
# self.upper_close_btn.move(233, 153) # 上位料斗的关按钮位置
|
||||
|
||||
self.upper_arch_btn = CircularButton("拱", group)
|
||||
self.upper_arch_btn.move(290, 37) # 上位料斗的破拱按钮位置
|
||||
@ -167,6 +189,17 @@ class HopperWidget(QWidget):
|
||||
self.upper_arch_btn.clicked.connect(self.onUpperArchBreaking)
|
||||
self.lower_arch_btn.clicked.connect(self.onLowerArchBreaking)
|
||||
|
||||
self.upper_open_btn.clicked.connect(self.onUpperClampOpen)
|
||||
self.lower_open_btn.clicked.connect(self.onLowerClampOpen)
|
||||
|
||||
@Slot()
|
||||
def onUpperClampOpen(self):
|
||||
self.upper_clamp_widget.testAnimation(target_angle=60, duration=6) # 测试,6秒打开60度
|
||||
|
||||
@Slot()
|
||||
def onLowerClampOpen(self):
|
||||
self.lower_clamp_widget.testAnimation(target_angle=25, duration=6) # 测试,6秒打开30度
|
||||
|
||||
@Slot()
|
||||
def onUpperArchBreaking(self):
|
||||
if self.upper_arch_breaking_status == False: # 不破拱状态
|
||||
@ -199,7 +232,8 @@ class HopperWidget(QWidget):
|
||||
|
||||
# 下位的 料斗控件的固定尺寸 !!!!
|
||||
# 注意: 这里需要根据具体的料斗图片来修改
|
||||
group.setFixedSize(332, 280)
|
||||
# group.setFixedSize(332, 280)
|
||||
group.setFixedSize(332, 222)
|
||||
# group.setStyleSheet("background-color: black")
|
||||
|
||||
layout = QVBoxLayout(group)
|
||||
@ -208,13 +242,22 @@ class HopperWidget(QWidget):
|
||||
|
||||
# 标题标签(下位)
|
||||
self.lower_title_label = QLabel("低位料斗")
|
||||
self.lower_title_label.setFixedSize(79, 17)
|
||||
self.lower_title_label.setFixedSize(117, 23)
|
||||
self.lower_title_label.setAlignment(Qt.AlignCenter)
|
||||
self.lower_title_label.setStyleSheet("color: #2fd3f2; font-size: 14px; font-weight: bold;")
|
||||
# self.lower_title_label.setStyleSheet("color: #2fd3f2; font-size: 14px; font-weight: bold;")
|
||||
# self.lower_title_label.setStyleSheet("color: #0bffff; font-size: 18px;")
|
||||
self.lower_title_label.setStyleSheet("""
|
||||
color: #0bffff;
|
||||
font-size: 18px;
|
||||
background-image: url(:/icons/images/文字标题底.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
margin-bottom: 4px;
|
||||
""")
|
||||
layout.addWidget(self.lower_title_label, alignment=Qt.AlignCenter)
|
||||
|
||||
# 加载外框图片
|
||||
outer_img = ":/icons/images/下位料斗1.png"
|
||||
outer_img = ":/icons/images/料斗1.png"
|
||||
outer_pixmap = QPixmap(outer_img)
|
||||
if outer_pixmap.isNull():
|
||||
print(f"警告:图片 {outer_img} 加载失败,请检查路径!")
|
||||
@ -229,7 +272,7 @@ class HopperWidget(QWidget):
|
||||
layout.addWidget(self.lower_bg_widget, alignment=Qt.AlignCenter)
|
||||
|
||||
# 内框图片(下位)
|
||||
inner_img = ":/icons/images/下位料斗2.png"
|
||||
inner_img = ":/icons/images/料斗2.png"
|
||||
inner_pixmap = QPixmap(inner_img)
|
||||
if not inner_pixmap.isNull():
|
||||
self.lower_inner_label = QLabel(self.lower_bg_widget)
|
||||
@ -237,14 +280,14 @@ class HopperWidget(QWidget):
|
||||
self.lower_inner_label.setFixedSize(inner_pixmap.width(), inner_pixmap.height())
|
||||
self.lower_inner_label.move(14, 9)
|
||||
|
||||
# 状态图片(下位,红色)
|
||||
# 状态图片(下位)
|
||||
status_img = ":/icons/images/料斗状态绿.png"
|
||||
status_pixmap = QPixmap(status_img)
|
||||
if not status_pixmap.isNull():
|
||||
status_pixmap = status_pixmap.scaled(22, 22, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||
status_pixmap = status_pixmap.scaled(13, 13, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||
self.lower_status_label = QLabel(self.lower_bg_widget)
|
||||
self.lower_status_label.setPixmap(status_pixmap)
|
||||
self.lower_status_label.move(32, 18)
|
||||
self.lower_status_label.move(22, 12)
|
||||
self.lower_status_label.setScaledContents(False)
|
||||
self.lower_status_label.setStyleSheet("background: none;")
|
||||
|
||||
@ -252,29 +295,35 @@ class HopperWidget(QWidget):
|
||||
arch_img = ":/icons/images/破拱.png"
|
||||
arch_pixmap = QPixmap(arch_img)
|
||||
if not arch_pixmap.isNull():
|
||||
arch_pixmap = arch_pixmap.scaled(24, 21, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||
arch_pixmap = arch_pixmap.scaled(16, 13, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||
self.lower_arch_label = QLabel(self.lower_bg_widget)
|
||||
self.lower_arch_label.setPixmap(arch_pixmap)
|
||||
self.lower_arch_label.move(outer_width - 56, 18)
|
||||
self.lower_arch_label.move(outer_width - 39, 12)
|
||||
self.lower_arch_label.setStyleSheet("background: none;")
|
||||
self.lower_arch_label.setHidden(True) # 初始,不破拱状态,隐藏
|
||||
|
||||
# 重量文字(下位)
|
||||
self.lower_weight_label = QLabel("5000kg", self.lower_bg_widget)
|
||||
self.lower_weight_label.setStyleSheet("background: none; background-color: #262c38; color: #79c053; font-size: 14px;font-weight: bold;")
|
||||
# self.lower_weight_label.setStyleSheet("background: none; background-color: #262c38; color: #79c053; font-size: 14px;font-weight: bold;")
|
||||
self.lower_weight_label.setStyleSheet("background: none; background-color: #003669; color: #16ffff; font-size: 18px;")
|
||||
self.lower_weight_label.setAlignment(Qt.AlignCenter)
|
||||
self.lower_weight_label.setFixedSize(93, 22)
|
||||
self.lower_weight_label.move(outer_width//2 - 46, outer_height//2 - 46)
|
||||
# self.lower_weight_label.setFixedSize(93, 22)
|
||||
self.lower_weight_label.setFixedSize(120, 29)
|
||||
# self.lower_weight_label.move(outer_width//2 - 46, outer_height//2 - 46)
|
||||
self.lower_weight_label.move(outer_width//2 - 60, outer_height//2 - 46)
|
||||
|
||||
# 额外文字(下位)
|
||||
self.lower_extra_label = QLabel("开: 25°", self.lower_bg_widget)
|
||||
self.lower_extra_label.setAlignment(Qt.AlignCenter)
|
||||
self.lower_extra_label.setStyleSheet("background: none; background-color: #262c38; color: #79c053; font-size: 14px;font-weight: bold;")
|
||||
self.lower_extra_label.setFixedSize(93, 22)
|
||||
self.lower_extra_label.move(outer_width//2 - 46, outer_height//2 - 20)
|
||||
# self.lower_extra_label.setStyleSheet("background: none; background-color: #262c38; color: #79c053; font-size: 14px;font-weight: bold;")
|
||||
self.lower_extra_label.setStyleSheet("background: none; background-color: #003669; color: #16ffff; font-size: 18px;")
|
||||
# self.lower_extra_label.setFixedSize(93, 22)
|
||||
self.lower_extra_label.setFixedSize(120, 29)
|
||||
# self.lower_extra_label.move(outer_width//2 - 46, outer_height//2 - 20)
|
||||
self.lower_extra_label.move(outer_width//2 - 60, outer_height//2 - 13)
|
||||
|
||||
# 料斗夹具图片(下位)
|
||||
# clamp_img = ":/icons/images/料斗夹具.png"
|
||||
# clamp_img = "料斗夹具.png"
|
||||
# clamp_pixmap = QPixmap(clamp_img)
|
||||
# if not clamp_pixmap.isNull():
|
||||
# self.lower_clamp_label = QLabel()
|
||||
@ -283,29 +332,35 @@ class HopperWidget(QWidget):
|
||||
# layout.addWidget(self.lower_clamp_label, alignment=Qt.AlignCenter)
|
||||
|
||||
# 目前由于给出的 料斗夹具图片尺寸大了,只能够先缩放,后期图片对了,可以用上面的
|
||||
clamp_img = ":/icons/images/料斗夹具.png"
|
||||
clamp_pixmap = QPixmap(clamp_img)
|
||||
if not clamp_pixmap.isNull():
|
||||
self.lower_clamp_label = QLabel()
|
||||
# 1. 固定QLabel尺寸为152x60
|
||||
self.lower_clamp_label.setFixedSize(152, 60)
|
||||
# 2. 图片缩放到152x60(保持比例,平滑缩放)
|
||||
scaled_pixmap = clamp_pixmap.scaled(
|
||||
152, 60,
|
||||
Qt.KeepAspectRatio, # 保持图片原始比例,避免变形
|
||||
Qt.SmoothTransformation # 平滑缩放,提升显示效果
|
||||
)
|
||||
self.lower_clamp_label.setPixmap(scaled_pixmap)
|
||||
# 3. 确保图片在QLabel中居中显示
|
||||
self.lower_clamp_label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(self.lower_clamp_label, alignment=Qt.AlignCenter)
|
||||
# clamp_img = "料斗夹具.png"
|
||||
# clamp_pixmap = QPixmap(clamp_img)
|
||||
# if not clamp_pixmap.isNull():
|
||||
# self.lower_clamp_label = QLabel()
|
||||
# # 1. 固定QLabel尺寸为152x60
|
||||
# self.lower_clamp_label.setFixedSize(152, 60)
|
||||
# # 2. 图片缩放到152x60(保持比例,平滑缩放)
|
||||
# scaled_pixmap = clamp_pixmap.scaled(
|
||||
# 152, 60,
|
||||
# Qt.KeepAspectRatio, # 保持图片原始比例,避免变形
|
||||
# Qt.SmoothTransformation # 平滑缩放,提升显示效果
|
||||
# )
|
||||
# self.lower_clamp_label.setPixmap(scaled_pixmap)
|
||||
# # 3. 确保图片在QLabel中居中显示
|
||||
# self.lower_clamp_label.setAlignment(Qt.AlignCenter)
|
||||
# layout.addWidget(self.lower_clamp_label, alignment=Qt.AlignCenter)
|
||||
|
||||
# 夹具容器
|
||||
self.lower_clamp_widget = ClampWidget()
|
||||
layout.addWidget( self.lower_clamp_widget, alignment=Qt.AlignCenter)
|
||||
self.lower_bg_widget.raise_() # 夹具图层位于 self.lower_bg_widget之下
|
||||
|
||||
# 按钮(下位)
|
||||
self.lower_open_btn = CircularButton("开", group)
|
||||
self.lower_open_btn.move(60, 233) # 下位料斗的开按钮位置
|
||||
# self.lower_open_btn.move(60, 233) # 下位料斗的开按钮位置
|
||||
self.lower_open_btn.move(290, 90)
|
||||
|
||||
self.lower_close_btn = CircularButton("关", group)
|
||||
self.lower_close_btn.move(233, 233) # 下位料斗的关按钮位置
|
||||
# self.lower_close_btn = CircularButton("关", group)
|
||||
# self.lower_close_btn.move(233, 233) # 下位料斗的关按钮位置
|
||||
|
||||
self.lower_arch_btn = CircularButton("拱", group)
|
||||
self.lower_arch_btn.move(290, 34) # 下位料斗的破拱按钮位置
|
||||
@ -395,6 +450,23 @@ class HopperWidget(QWidget):
|
||||
)
|
||||
self.lower_status_label.setPixmap(status_pixmap)
|
||||
|
||||
# 隐藏上料斗 (用于上料斗移动)
|
||||
def hideUpperHopper(self):
|
||||
self.upper_title_label.hide()
|
||||
self.upper_bg_widget.hide()
|
||||
self.upper_clamp_widget.hide()
|
||||
self.upper_open_btn.hide()
|
||||
self.upper_arch_btn.hide()
|
||||
|
||||
# 显示上料斗 (用于上料斗移动完成后恢复显示)
|
||||
def showUpperHopper(self):
|
||||
self.upper_title_label.setHidden(False)
|
||||
self.upper_bg_widget.setHidden(False)
|
||||
self.upper_clamp_widget.setHidden(False)
|
||||
self.upper_open_btn.setHidden(False)
|
||||
self.upper_arch_btn.setHidden(False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
window = HopperWidget()
|
||||
|
||||
61
view/widgets/mixer_widget.py
Normal file
61
view/widgets/mixer_widget.py
Normal file
@ -0,0 +1,61 @@
|
||||
from PySide6.QtWidgets import QWidget, QLabel, QHBoxLayout
|
||||
from PySide6.QtGui import QPixmap, QFont
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
import resources.resources_rc
|
||||
|
||||
class MixerWidget(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
# 初始化布局
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
self.setFixedSize(225, 102)
|
||||
|
||||
# 1. 创建“搅拌机”文字标签
|
||||
self.text_label = QLabel("搅拌机")
|
||||
self.text_label.setFixedSize(100, 23)
|
||||
self.text_label.setFont(QFont("Arial", 14)) # 设置字体大小为16px
|
||||
self.text_label.setAlignment(Qt.AlignCenter)
|
||||
self.text_label.setStyleSheet("""
|
||||
background-image: url(:/icons/images/文字标题底.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
color: #0bffff; /* 可根据需求调整文字颜色 */
|
||||
""")
|
||||
layout.addWidget(self.text_label, alignment=Qt.AlignLeft)
|
||||
|
||||
# 2. 创建搅拌机设备及搅拌桨图标
|
||||
self.device_label = QLabel()
|
||||
device_pixmap = QPixmap(":/icons/images/搅拌机.png")
|
||||
self.device_label.setPixmap(device_pixmap)
|
||||
layout.addWidget(self.device_label, alignment=Qt.AlignLeft)
|
||||
|
||||
# 3. 叠加两个搅拌桨图标
|
||||
self.blade1 = QLabel(self.device_label) # 从左往右第一个搅拌桨
|
||||
blade1_pixmap = QPixmap(":/icons/images/搅拌桨.png")
|
||||
self.blade1.setPixmap(blade1_pixmap)
|
||||
self.blade1.move(
|
||||
(device_pixmap.width() - blade1_pixmap.width()) // 2 - 26,
|
||||
(device_pixmap.height() - blade1_pixmap.height()) // 2 - 4
|
||||
)
|
||||
|
||||
self.blade2 = QLabel(self.device_label)
|
||||
blade2_pixmap = QPixmap(":/icons/images/搅拌桨.png") # 从左往右第二个搅拌桨
|
||||
self.blade2.setPixmap(blade2_pixmap)
|
||||
self.blade2.move(
|
||||
(device_pixmap.width() - blade2_pixmap.width()) // 2 + 31,
|
||||
(device_pixmap.height() - blade2_pixmap.height()) // 2 - 4
|
||||
)
|
||||
|
||||
# 测试代码
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
from PySide6.QtWidgets import QApplication, QMainWindow
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
mixer_widget = MixerWidget()
|
||||
mixer_widget.show()
|
||||
sys.exit(app.exec())
|
||||
168
view/widgets/plan_widget.py
Normal file
168
view/widgets/plan_widget.py
Normal file
@ -0,0 +1,168 @@
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QApplication)
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QPainter, QPixmap
|
||||
from .value_adjuster import ValueAdjuster
|
||||
from .switch_button import SwitchButton
|
||||
import sys
|
||||
|
||||
import resources.resources_rc
|
||||
|
||||
class PlanWidget(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
# 加载背景图
|
||||
self.bg_pixmap = QPixmap(":/icons/images/任务计划背景.png")
|
||||
self.setFixedSize(self.bg_pixmap.size())
|
||||
|
||||
# 存储可修改控件的字典
|
||||
self.controls = {
|
||||
"plan_no": None, # 计划单号
|
||||
"volume": None, # 计划方量(ValueAdjuster)
|
||||
"ratio": None, # 计划配比
|
||||
"status": None, # 下发状态
|
||||
"auto_dispatch": None # 自动派单(SwitchButton)
|
||||
}
|
||||
|
||||
# 主垂直布局
|
||||
main_layout = QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(13, 5, 6, 15)
|
||||
main_layout.setSpacing(0)
|
||||
|
||||
# 第一行:计划单号
|
||||
row1_layout = QHBoxLayout()
|
||||
row1_layout.setSpacing(0)
|
||||
status_icon1 = QLabel()
|
||||
status_icon1.setPixmap(QPixmap(":/icons/images/任务矩形4.png"))
|
||||
status_icon1.setFixedSize(6, 6)
|
||||
row1_layout.addWidget(status_icon1)
|
||||
row1_layout.addSpacing(4)
|
||||
|
||||
label1 = QLabel("计划单号")
|
||||
label1.setStyleSheet("font-size: 18px; color: #03f5ff;")
|
||||
label1.setFixedSize(73, 20)
|
||||
row1_layout.addWidget(label1, alignment=Qt.AlignLeft)
|
||||
|
||||
value1 = QLabel("PD0001")
|
||||
value1.setStyleSheet("font-size: 18px; color: white;")
|
||||
row1_layout.addWidget(value1)
|
||||
main_layout.addLayout(row1_layout)
|
||||
self.controls["plan_no"] = value1
|
||||
|
||||
# 第二行:计划方量
|
||||
row2_layout = QHBoxLayout()
|
||||
status_icon2 = QLabel()
|
||||
status_icon2.setPixmap(QPixmap(":/icons/images/任务矩形1.png"))
|
||||
status_icon2.setFixedSize(6, 6)
|
||||
row2_layout.addWidget(status_icon2)
|
||||
|
||||
row2_layout.addSpacing(4)
|
||||
label2 = QLabel("计划方量")
|
||||
label2.setStyleSheet("font-size: 18px; color: #03f5ff;")
|
||||
row2_layout.addWidget(label2)
|
||||
|
||||
self.fangliang_adjuster = ValueAdjuster() # 导入的方量调整控件
|
||||
row2_layout.addWidget(self.fangliang_adjuster)
|
||||
main_layout.addLayout(row2_layout)
|
||||
self.controls["volume"] = self.fangliang_adjuster
|
||||
|
||||
# 第三行:计划配比
|
||||
row3_layout = QHBoxLayout()
|
||||
status_icon3 = QLabel()
|
||||
status_icon3.setFixedSize(6, 6)
|
||||
status_icon3.setPixmap(QPixmap(":/icons/images/任务矩形2.png"))
|
||||
row3_layout.addWidget(status_icon3)
|
||||
row3_layout.addSpacing(4)
|
||||
|
||||
label3 = QLabel("计划配比")
|
||||
label3.setStyleSheet("font-size: 18px; color: #03f5ff;")
|
||||
row3_layout.addWidget(label3)
|
||||
|
||||
value3 = QLabel("C55P12")
|
||||
value3.setStyleSheet("font-size: 18px; color: white;")
|
||||
row3_layout.addWidget(value3)
|
||||
main_layout.addLayout(row3_layout)
|
||||
# 存入字典
|
||||
self.controls["ratio"] = value3
|
||||
|
||||
# 第四行:下发状态
|
||||
row4_layout = QHBoxLayout()
|
||||
status_icon4 = QLabel()
|
||||
status_icon4.setFixedSize(6, 6)
|
||||
status_icon4.setPixmap(QPixmap(":/icons/images/任务矩形3.png"))
|
||||
row4_layout.addWidget(status_icon4)
|
||||
|
||||
row4_layout.addSpacing(4)
|
||||
label4 = QLabel("下发状态")
|
||||
label4.setStyleSheet("font-size: 18px; color: #03f5ff;")
|
||||
row4_layout.addWidget(label4)
|
||||
|
||||
value4 = QLabel("计划中")
|
||||
value4.setStyleSheet("font-size: 18px; color: white;")
|
||||
row4_layout.addWidget(value4)
|
||||
main_layout.addLayout(row4_layout)
|
||||
self.controls["status"] = value4
|
||||
|
||||
# 第五行:自动派单
|
||||
row5_layout = QHBoxLayout()
|
||||
status_icon5 = QLabel()
|
||||
status_icon5.setPixmap(QPixmap(":/icons/images/任务矩形5.png"))
|
||||
status_icon5.setFixedSize(6, 6)
|
||||
row5_layout.addWidget(status_icon5)
|
||||
row5_layout.addSpacing(4)
|
||||
|
||||
label5 = QLabel("自动派单")
|
||||
label5.setStyleSheet("font-size: 18px; color: #03f5ff;")
|
||||
row5_layout.addWidget(label5)
|
||||
|
||||
self.switch = SwitchButton()
|
||||
self.switch.setChecked(True)
|
||||
row5_layout.addWidget(self.switch, alignment=Qt.AlignLeft)
|
||||
main_layout.addLayout(row5_layout)
|
||||
self.controls["auto_dispatch"] = self.switch
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""绘制背景图片"""
|
||||
painter = QPainter(self)
|
||||
painter.drawPixmap(self.rect(), self.bg_pixmap)
|
||||
super().paintEvent(event)
|
||||
|
||||
# ------------------- 对外修改接口 -------------------
|
||||
def set_plan_no(self, new_no:str):
|
||||
"""修改计划单号"""
|
||||
if self.controls["plan_no"]:
|
||||
self.controls["plan_no"].setText(new_no)
|
||||
|
||||
def set_plan_volume(self, new_volume:float):
|
||||
"""修改计划方量"""
|
||||
if self.controls["volume"]:
|
||||
self.controls["volume"].set_value(new_volume)
|
||||
|
||||
def set_plan_ratio(self, new_ratio:str):
|
||||
"""修改计划配比"""
|
||||
if self.controls["ratio"]:
|
||||
self.controls["ratio"].setText(new_ratio)
|
||||
|
||||
def set_status(self, new_status:str):
|
||||
"""修改下发状态"""
|
||||
if self.controls["status"]:
|
||||
self.controls["status"].setText(new_status)
|
||||
|
||||
def set_auto_dispatch(self, is_checked:bool):
|
||||
"""修改自动派单开关状态, true: 开启自动派单, False: 关闭自动派单"""
|
||||
if self.controls["auto_dispatch"]:
|
||||
self.controls["auto_dispatch"].setChecked(is_checked)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
widget = PlanWidget()
|
||||
widget.show()
|
||||
|
||||
# 测试接口
|
||||
widget.set_plan_no("PD0002") # 计划单号
|
||||
widget.set_plan_ratio("C60P15") # 计划配比
|
||||
widget.set_plan_volume(5) # 计划方量
|
||||
widget.set_status("已下发") # 下发状态
|
||||
widget.set_auto_dispatch(False) # 自动派单
|
||||
|
||||
sys.exit(app.exec())
|
||||
@ -1,46 +1,47 @@
|
||||
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QHBoxLayout
|
||||
from PySide6.QtGui import QPixmap,QRegion,QPainter, QColor, QPainterPath
|
||||
from PySide6.QtGui import QPixmap, QRegion, QPainter, QColor, QPainterPath
|
||||
from PySide6.QtCore import Qt, QPropertyAnimation, Property, QSize, QRectF
|
||||
import sys
|
||||
|
||||
|
||||
class MaskedLabel(QLabel):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setFixedSize(28, 20) # 遮罩大小
|
||||
self.setFixedSize(28, 20) # 遮罩大小
|
||||
self.setStyleSheet("background-color: transparent; border: none;")
|
||||
|
||||
def paintEvent(self, event):
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.Antialiasing)
|
||||
|
||||
parent_widget = self.parent()
|
||||
bg_color = QColor("#f3f3f3") # 默认颜色
|
||||
if parent_widget:
|
||||
# 从父控件的调色板中提取背景色
|
||||
bg_color = parent_widget.palette().color(parent_widget.backgroundRole())
|
||||
|
||||
|
||||
# parent_widget = self.parent()
|
||||
bg_color = QColor("#023eab") # 默认颜色 注意:需要根据背景颜色修改
|
||||
# if parent_widget:
|
||||
# # 从父控件的调色板中提取背景色
|
||||
# bg_color = parent_widget.palette().color(parent_widget.backgroundRole())
|
||||
|
||||
# 用父控件背景色绘制遮罩
|
||||
painter.setBrush(bg_color)
|
||||
painter.setPen(Qt.NoPen)
|
||||
|
||||
|
||||
# 步骤3:绘制遮罩路径(逻辑不变)
|
||||
path = QPainterPath()
|
||||
mask_rect = QRectF(0, 0, 28, 20)
|
||||
circle_radius = 10
|
||||
circle_center_x = 28
|
||||
circle_center_y = 10
|
||||
|
||||
|
||||
path.addRect(mask_rect)
|
||||
path.addEllipse(
|
||||
circle_center_x - circle_radius,
|
||||
circle_center_y - circle_radius,
|
||||
circle_radius * 2,
|
||||
circle_radius * 2
|
||||
circle_radius * 2,
|
||||
)
|
||||
path.setFillRule(Qt.OddEvenFill)
|
||||
|
||||
|
||||
painter.drawPath(path)
|
||||
|
||||
|
||||
|
||||
class LinearProductionProgress(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
@ -48,7 +49,7 @@ class LinearProductionProgress(QWidget):
|
||||
self._progress = 0 # 进度(0-100)
|
||||
self.setFixedSize(450 + 18, 20)
|
||||
self.setStyleSheet(
|
||||
"""
|
||||
"""
|
||||
border-radius: 9px;
|
||||
"""
|
||||
)
|
||||
@ -56,18 +57,31 @@ class LinearProductionProgress(QWidget):
|
||||
# 底层背景
|
||||
self.bg_label = QLabel(self)
|
||||
self.bg_label.setFixedSize(450, 20)
|
||||
# self.bg_label.setStyleSheet(
|
||||
# """
|
||||
# background-color: #e7e7e7;
|
||||
# """
|
||||
# )
|
||||
# #011454
|
||||
self.bg_label.setStyleSheet(
|
||||
"""
|
||||
background-color: #e7e7e7;
|
||||
"""
|
||||
background-color: #011454;
|
||||
"""
|
||||
)
|
||||
self.bg_label.move(18, 0)
|
||||
self.bg_label.move(17, 0) # 这里需要调整对齐
|
||||
|
||||
# 上层进度填充
|
||||
self.fg_label = QLabel(self)
|
||||
# self.fg_label.setStyleSheet(
|
||||
# """
|
||||
# background-color: #0052d9;
|
||||
# min-width: 18px;
|
||||
# """
|
||||
# )
|
||||
# #02f2fe
|
||||
self.fg_label.setStyleSheet(
|
||||
"""
|
||||
background-color: #0052d9;
|
||||
background-color: #02f2fe;
|
||||
min-width: 18px;
|
||||
"""
|
||||
)
|
||||
@ -80,15 +94,24 @@ class LinearProductionProgress(QWidget):
|
||||
self.percent_label.setText("0%")
|
||||
self.percent_label.setAlignment(Qt.AlignCenter)
|
||||
self.percent_label.setFixedSize(33, 19)
|
||||
# self.percent_label.setStyleSheet(
|
||||
# """
|
||||
# color: white;
|
||||
# font-size: 12px;
|
||||
# font-weight: bold;
|
||||
# background-color: transparent;
|
||||
# """
|
||||
# )
|
||||
# #02366c #001c83
|
||||
self.percent_label.setStyleSheet(
|
||||
"""
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
color: #001c83;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
background-color: transparent;
|
||||
"""
|
||||
)
|
||||
self.percent_label.move(18, 0) # 百分比标签初始位置
|
||||
self.percent_label.move(18, 0) # 百分比标签初始位置
|
||||
self.percent_label.raise_()
|
||||
|
||||
# 遮盖左侧区域
|
||||
@ -111,17 +134,17 @@ class LinearProductionProgress(QWidget):
|
||||
# 注意:为了实现前4%的动态效果,需要从 fd_width的4%起开始计算
|
||||
fg_width = int(450 * (self._progress + 4) / 100)
|
||||
self.fg_label.setFixedWidth(fg_width)
|
||||
|
||||
|
||||
# 计算百分比标签位置:进度条右边缘 - 9px(偏移) - 标签宽度(33px)
|
||||
if fg_width > 60: # 当上层进度条宽度大于60px,开始移动
|
||||
label_x = fg_width - 9 - 33
|
||||
if fg_width > 60: # 当上层进度条宽度大于60px,开始移动
|
||||
label_x = fg_width - 9 - 33
|
||||
# 移动百分比标签
|
||||
self.percent_label.move(label_x, 0)
|
||||
else:
|
||||
# 复原百分比标签
|
||||
# 移动回初始位置
|
||||
self.percent_label.move(18, 0)
|
||||
|
||||
|
||||
# 设置百分比标签
|
||||
self.percent_label.setText(f"{self._progress}%")
|
||||
|
||||
@ -129,21 +152,22 @@ class LinearProductionProgress(QWidget):
|
||||
class ProductionProgressWidget(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setFixedSize(620, 49) # 进度条控件大小
|
||||
self.setFixedSize(620, 49) # 进度条控件大小
|
||||
|
||||
# 左侧文字标签
|
||||
self.text_label = QLabel("生产进度")
|
||||
self.text_label = QLabel("进度")
|
||||
self.text_label.setFixedSize(112, 29)
|
||||
self.text_label.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
|
||||
self.text_label.setStyleSheet("font-family: 'Microsoft YaHei';font-size: 18px;")
|
||||
self.text_label.setAlignment(Qt.AlignTop | Qt.AlignRight)
|
||||
self.text_label.setStyleSheet("font-family: 'Microsoft YaHei';font-size: 20px;color: #16FFFF;")
|
||||
|
||||
# 右侧进度条
|
||||
self.linear_progress = LinearProductionProgress()
|
||||
|
||||
|
||||
self.main_layout = QHBoxLayout(self)
|
||||
self.main_layout.addWidget(self.text_label)
|
||||
self.main_layout.addWidget(self.linear_progress)
|
||||
self.main_layout.addWidget(self.linear_progress, alignment=Qt.AlignLeft)
|
||||
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.main_layout.setSpacing(0)
|
||||
|
||||
def testProgress(self, seconds: float):
|
||||
self.animation = QPropertyAnimation(self.linear_progress, b"progress")
|
||||
@ -164,6 +188,6 @@ class ProductionProgressWidget(QWidget):
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
window = ProductionProgressWidget()
|
||||
# window.testProgress(60) # 进度条测试
|
||||
window.testProgress(60) # 进度条测试
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
|
||||
@ -4,7 +4,7 @@ from PySide6.QtCore import Qt
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
import resources.resource_rc
|
||||
import resources.resources_rc
|
||||
|
||||
class DeviceStatusPopup(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
|
||||
126
view/widgets/switch_button.py
Normal file
126
view/widgets/switch_button.py
Normal file
@ -0,0 +1,126 @@
|
||||
from PySide6.QtWidgets import (
|
||||
QAbstractButton,
|
||||
QApplication,
|
||||
QSizePolicy,
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
)
|
||||
from PySide6.QtCore import Qt, QRect, Signal
|
||||
from PySide6.QtGui import QPainter, QBrush
|
||||
|
||||
|
||||
class SwitchButton(QAbstractButton):
|
||||
# 开关切换信号
|
||||
# 开: True, 关: False
|
||||
switched = Signal(bool)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._checked = False
|
||||
|
||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
|
||||
# 颜色映射
|
||||
self.color_keymap = {
|
||||
"slider": "#16ffff",
|
||||
"text": Qt.GlobalColor.white,
|
||||
"on_bg": "#008ee8", # 开的时候的背景颜色
|
||||
"off_bg": "#001c83", # 关的时候的背景颜色
|
||||
}
|
||||
|
||||
self.clicked.connect(self.onClicked)
|
||||
self._init_style() # 新尺寸的样式表
|
||||
self._init_size_policy()
|
||||
|
||||
def _init_style(self):
|
||||
self.setStyleSheet(
|
||||
"""
|
||||
SwitchButton {
|
||||
font-family: "Microsoft YaHei";
|
||||
font-size: 11px;
|
||||
color: white;
|
||||
border-radius: 9px;
|
||||
margin: 2px;
|
||||
min-width: 39px;
|
||||
max-width: 39px;
|
||||
min-height: 18px;
|
||||
max-height: 18px;
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
def _init_size_policy(self):
|
||||
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
|
||||
def paintEvent(self, event):
|
||||
# 1. 调整滑块间距
|
||||
slider_space = 1 # 滑块与背景的间距(上下左右各1px)
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
|
||||
|
||||
try:
|
||||
# 2. 绘制背景
|
||||
background_rect = self.rect()
|
||||
painter.setPen(Qt.PenStyle.NoPen)
|
||||
bg_color = (
|
||||
self.color_keymap["on_bg"]
|
||||
if self._checked
|
||||
else self.color_keymap["off_bg"]
|
||||
)
|
||||
painter.setBrush(QBrush(bg_color))
|
||||
painter.drawRoundedRect(
|
||||
background_rect, self.height() / 2, self.height() / 2
|
||||
)
|
||||
|
||||
# 3. 计算新滑块尺寸
|
||||
slider_width = self.height() - slider_space * 2
|
||||
|
||||
# 4. 计算新滑块位置
|
||||
if self._checked:
|
||||
slider_x = self.width() - slider_width - slider_space
|
||||
else:
|
||||
slider_x = slider_space
|
||||
slider_y = slider_space
|
||||
|
||||
# 5. 绘制滑块
|
||||
slider_rect = QRect(slider_x, slider_y, slider_width, slider_width)
|
||||
painter.setBrush(QBrush(self.color_keymap["slider"]))
|
||||
painter.drawEllipse(slider_rect)
|
||||
finally:
|
||||
painter.end()
|
||||
|
||||
def onClicked(self):
|
||||
self._checked = not self._checked
|
||||
self.update()
|
||||
self.switched.emit(self._checked)
|
||||
|
||||
def setChecked(self, checked: bool):
|
||||
self._checked = checked
|
||||
self.update()
|
||||
self.switched.emit(self._checked)
|
||||
|
||||
def isChecked(self) -> bool:
|
||||
return self._checked
|
||||
|
||||
|
||||
# 测试示例
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
test_window = QWidget()
|
||||
test_window.setWindowTitle("开关测试")
|
||||
test_window.resize(200, 100)
|
||||
|
||||
layout = QVBoxLayout(test_window)
|
||||
layout.setContentsMargins(50, 30, 50, 30)
|
||||
|
||||
switch = SwitchButton()
|
||||
switch.switched.connect(
|
||||
lambda state: print(f"开关状态:{'选中' if state else '未选中'}")
|
||||
)
|
||||
layout.addWidget(switch)
|
||||
|
||||
test_window.show()
|
||||
sys.exit(app.exec())
|
||||
@ -3,7 +3,7 @@ from PySide6.QtGui import QIcon
|
||||
from PySide6.QtCore import QSize, Qt
|
||||
import sys
|
||||
|
||||
import resources.resource_rc
|
||||
import resources.resources_rc
|
||||
|
||||
class SystemButtonWidget(QWidget):
|
||||
def __init__(self):
|
||||
|
||||
151
view/widgets/system_center_dialog.py
Normal file
151
view/widgets/system_center_dialog.py
Normal file
@ -0,0 +1,151 @@
|
||||
from PySide6.QtWidgets import (QApplication, QVBoxLayout, QPushButton, QDialog)
|
||||
from PySide6.QtGui import QPixmap, QFont, QIcon, QPainter
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtCore import Qt, Signal, QPropertyAnimation, QEasingCurve, QRect
|
||||
import sys
|
||||
|
||||
import resources.resources_rc
|
||||
|
||||
class CustomButton(QPushButton):
|
||||
def __init__(self, text, normal_icon, active_icon, parent=None):
|
||||
super().__init__(text, parent)
|
||||
self.normal_icon = normal_icon
|
||||
self.active_icon = active_icon
|
||||
self.setFixedSize(147, 36)
|
||||
self.setCursor(Qt.PointingHandCursor)
|
||||
self.update_icon(self.normal_icon)
|
||||
self.setFont(QFont("", 17))
|
||||
self.setStyleSheet("""
|
||||
QPushButton {
|
||||
color: #16ffff;
|
||||
background: none;
|
||||
outline: none;
|
||||
border: none;
|
||||
text-align: center;
|
||||
background-color: transparent;
|
||||
font-weight: Bold;
|
||||
}
|
||||
QPushButton:hover {
|
||||
color: #001c83;
|
||||
background-color: #16ffff;
|
||||
background-repeat: no-repeat;
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
""")
|
||||
|
||||
def update_icon(self, icon_path):
|
||||
pixmap = QPixmap(icon_path)
|
||||
if not pixmap.isNull():
|
||||
scaled_pixmap = pixmap.scaled(24, 24, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||
self.setIcon(QIcon(scaled_pixmap))
|
||||
self.setIconSize(scaled_pixmap.size())
|
||||
else:
|
||||
self.setIcon(QIcon())
|
||||
|
||||
def enterEvent(self, event):
|
||||
self.update_icon(self.active_icon)
|
||||
super().enterEvent(event)
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self.update_icon(self.normal_icon)
|
||||
super().leaveEvent(event)
|
||||
|
||||
class SystemCenterDialog(QDialog):
|
||||
# 定义三个信号,对应三个按钮的点击事件
|
||||
sys_setting_clicked = Signal() # 系统设置点击信号
|
||||
data_center_clicked = Signal() # 数据中心点击信号
|
||||
user_center_clicked = Signal() # 用户中心点击信号
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.init_ui()
|
||||
self.init_animations() # 初始化动画
|
||||
|
||||
def init_ui(self):
|
||||
# 弹窗基础设置
|
||||
self.setWindowTitle("系统中心")
|
||||
self.setWindowFlags(Qt.FramelessWindowHint) # 隐藏默认边框
|
||||
self.setWindowOpacity(0.0) # 初始状态为完全透明,实现动画效果
|
||||
|
||||
# 加载背景图
|
||||
self.background = QPixmap(":/icons/images/系统中心弹窗背景.png")
|
||||
if self.background.isNull():
|
||||
print("警告:系统中心弹窗背景.png 加载失败!")
|
||||
self.setFixedSize(220, 200)
|
||||
else:
|
||||
self.setFixedSize(self.background.size())
|
||||
|
||||
# 主布局
|
||||
main_layout = QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(0, 0, 0, 6)
|
||||
main_layout.setSpacing(0)
|
||||
main_layout.setAlignment(Qt.AlignCenter)
|
||||
|
||||
# 创建按钮并绑定信号发射
|
||||
self.sys_btn = CustomButton("系统设置", ":/icons/images/系统设置背景1.png", ":/icons/images/系统设置背景2.png")
|
||||
self.data_btn = CustomButton("数据中心", ":/icons/images/系统数据中心背景1.png", ":/icons/images/系统数据中心背景2.png")
|
||||
self.user_btn = CustomButton("用户中心", ":/icons/images/系统用户中心背景1.png", ":/icons/images/系统用户中心背景2.png")
|
||||
|
||||
# 按钮点击 → 发射对应信号(dialog不处理业务逻辑,只传递事件)
|
||||
self.sys_btn.clicked.connect(self.sys_setting_clicked.emit)
|
||||
self.data_btn.clicked.connect(self.data_center_clicked.emit)
|
||||
self.user_btn.clicked.connect(self.user_center_clicked.emit)
|
||||
|
||||
main_layout.addWidget(self.sys_btn)
|
||||
main_layout.addWidget(self.data_btn)
|
||||
main_layout.addWidget(self.user_btn)
|
||||
|
||||
def paintEvent(self, event):
|
||||
painter = QPainter(self)
|
||||
painter.drawPixmap(self.rect(), self.background)
|
||||
super().paintEvent(event)
|
||||
|
||||
def init_animations(self):
|
||||
"""初始化显示动画(可根据喜好选择或组合)"""
|
||||
# 1. 淡入动画(透明度从0→1)
|
||||
self.opacity_anim = QPropertyAnimation(self, b"windowOpacity")
|
||||
self.opacity_anim.setDuration(300) # 动画时长300ms
|
||||
self.opacity_anim.setStartValue(0.0)
|
||||
self.opacity_anim.setEndValue(1.0)
|
||||
self.opacity_anim.setEasingCurve(QEasingCurve.InOutCubic) # 缓动曲线(平滑加速减速)
|
||||
|
||||
# 2. 缩放动画(从80%→100%大小)
|
||||
self.scale_anim = QPropertyAnimation(self, b"geometry")
|
||||
self.scale_anim.setDuration(300)
|
||||
# 起点和终点在显示时动态设置(依赖当前弹窗位置)
|
||||
self.scale_anim.setEasingCurve(QEasingCurve.OutBack) # 带弹性的缓动曲线(弹出感)
|
||||
|
||||
# 3. 组合动画(同时执行淡入+缩放)
|
||||
from PySide6.QtCore import QParallelAnimationGroup
|
||||
self.anim_group = QParallelAnimationGroup(self)
|
||||
self.anim_group.addAnimation(self.opacity_anim)
|
||||
self.anim_group.addAnimation(self.scale_anim)
|
||||
|
||||
def showEvent(self, event):
|
||||
"""重写显示事件,每次显示时启动动画"""
|
||||
# 必须先调用父类showEvent,否则弹窗无法正常显示
|
||||
super().showEvent(event)
|
||||
|
||||
# 动态设置缩放动画的起点(基于当前弹窗位置和大小)
|
||||
current_geometry = self.geometry() # 弹窗当前位置和大小(已通过move设置)
|
||||
# 起点:缩小到80%,并保持中心位置不变
|
||||
start_rect = QRect(
|
||||
current_geometry.center().x() - current_geometry.width() * 0.4,
|
||||
current_geometry.center().y() - current_geometry.height() * 0.4,
|
||||
int(current_geometry.width() * 0.8),
|
||||
int(current_geometry.height() * 0.8)
|
||||
)
|
||||
self.scale_anim.setStartValue(start_rect)
|
||||
self.scale_anim.setEndValue(current_geometry) # 终点:原始大小
|
||||
|
||||
# 启动组合动画
|
||||
self.anim_group.start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
dialog = SystemCenterDialog()
|
||||
# 测试信号(实际业务在控制器中绑定)
|
||||
dialog.sys_setting_clicked.connect(lambda: print("系统设置被点击"))
|
||||
dialog.show()
|
||||
sys.exit(app.exec())
|
||||
117
view/widgets/system_nav_bar.py
Normal file
117
view/widgets/system_nav_bar.py
Normal file
@ -0,0 +1,117 @@
|
||||
from PySide6.QtWidgets import QWidget, QLabel, QHBoxLayout, QSpacerItem, QSizePolicy
|
||||
from PySide6.QtGui import QPixmap, QFont, QPainter
|
||||
from PySide6.QtCore import Qt, QTimer, QDateTime
|
||||
|
||||
import resources.resources_rc
|
||||
|
||||
# 自定义消息容器, 显示系统消息
|
||||
class MsgContainer(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setFixedSize(770, 24)
|
||||
|
||||
# 加载消息区域背景图
|
||||
self.bg_pixmap = QPixmap(":/icons/images/系统消息背景.png") # 替换为实际路径
|
||||
if self.bg_pixmap.isNull():
|
||||
print("警告:系统消息背景.png 加载失败")
|
||||
|
||||
# 消息区域内部布局(喇叭+文本)
|
||||
msg_layout = QHBoxLayout(self)
|
||||
msg_layout.setContentsMargins(0, 0, 0, 0) # 调整内边距,避免内容贴边
|
||||
msg_layout.setSpacing(3) # 喇叭和文本的间距
|
||||
|
||||
# 消息喇叭图标
|
||||
self.msg_icon = QLabel()
|
||||
self.msg_icon.setFixedSize(13, 18)
|
||||
# self.msg_icon.setStyleSheet("background-color:red;")
|
||||
self.msg_icon.setPixmap(QPixmap(":/icons/images/系统消息喇叭.png")) # 替换为实际路径
|
||||
msg_layout.addWidget(self.msg_icon, alignment=Qt.AlignVCenter | Qt.AlignLeft)
|
||||
|
||||
# 消息文本
|
||||
current_time = QDateTime.currentDateTime().toString("hh:mm:ss")
|
||||
self.msg_text = QLabel(f"{current_time} 开始启动智能浇筑系统")
|
||||
self.msg_text.setFixedWidth(740)
|
||||
# self.msg_text.setStyleSheet("color: white; font-size: 14px;background-color:red;") # 文本样式
|
||||
self.msg_text.setStyleSheet("color: white; font-size: 16px;font-weight:Bold;")
|
||||
self.msg_text.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||||
msg_layout.addWidget(self.msg_text)
|
||||
|
||||
def paintEvent(self, event):
|
||||
# 绘制消息区域背景图
|
||||
super().paintEvent(event) # 确保子控件正常绘制
|
||||
if self.bg_pixmap.isNull():
|
||||
return # 图片加载失败则不绘制
|
||||
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.SmoothPixmapTransform) # 缩放平滑
|
||||
painter.drawPixmap(self.rect(), self.bg_pixmap)
|
||||
|
||||
class SystemNavBar(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
# 设置尺寸
|
||||
self.setFixedSize(1280, 80)
|
||||
|
||||
# 1. 加载背景图
|
||||
self.bg_pixmap = QPixmap(":/icons/images/系统主界面导航栏.png") # 替换为实际图片路径
|
||||
if self.bg_pixmap.isNull():
|
||||
print("警告:背景图加载失败,请检查路径!")
|
||||
|
||||
main_layout = QHBoxLayout(self)
|
||||
main_layout.setContentsMargins(9, 9, 9, 19)
|
||||
main_layout.setSpacing(100) # 注意:左侧的logo+系统标题的容器 和 系统消息的间隔
|
||||
|
||||
# 左侧区域:logo + 系统标题
|
||||
left_container = QWidget()
|
||||
left_container.setFixedSize(400, 53)
|
||||
left_layout = QHBoxLayout(left_container) # 容器内部的水平布局
|
||||
left_layout.setContentsMargins(0, 0, 0, 0) # 容器内边距
|
||||
left_layout.setSpacing(6) # 设置logo和标题之间的间隙为6px
|
||||
# 系统logo
|
||||
self.logo = QLabel()
|
||||
self.logo.setFixedSize(53, 53)
|
||||
self.logo.setPixmap(QPixmap(":/icons/images/系统logo.png"))
|
||||
left_layout.addWidget(self.logo, alignment=Qt.AlignTop)
|
||||
# 系统总标题
|
||||
self.title = QLabel()
|
||||
self.title.setPixmap(QPixmap(":/icons/images/系统总标题.png"))
|
||||
left_layout.addWidget(self.title, alignment=Qt.AlignCenter)
|
||||
main_layout.addWidget(left_container, alignment=Qt.AlignTop)
|
||||
|
||||
# 中间区域:系统消息(喇叭+文本+背景)
|
||||
self.msg_container = MsgContainer()
|
||||
main_layout.addWidget(self.msg_container, alignment=Qt.AlignBottom | Qt.AlignRight)
|
||||
|
||||
# 右侧区域:实时时间
|
||||
self.time_label = QLabel()
|
||||
self.time_label.setStyleSheet("color: white; font-size: 16px;font-weight:Bold;")
|
||||
main_layout.addWidget(self.time_label, alignment= Qt.AlignTop | Qt.AlignRight)
|
||||
|
||||
# 启动时间更新定时器
|
||||
self.timer = QTimer(self)
|
||||
self.timer.timeout.connect(self.update_time)
|
||||
self.timer.start(1000) # 每秒更新一次
|
||||
|
||||
def paintEvent(self, event):
|
||||
# 2. 调用父类paintEvent
|
||||
super().paintEvent(event)
|
||||
|
||||
# 3. 创建画家对象,绘制背景图
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.SmoothPixmapTransform) # 缩放时平滑
|
||||
|
||||
painter.drawPixmap(0, 0, self.bg_pixmap)
|
||||
|
||||
def update_time(self):
|
||||
current_time = QDateTime.currentDateTime().toString("yyyy/MM/dd hh:mm:ss")
|
||||
self.time_label.setText(current_time)
|
||||
|
||||
# 测试代码
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
from PySide6.QtWidgets import QApplication, QMainWindow
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
nav_bar = SystemNavBar()
|
||||
nav_bar.show()
|
||||
sys.exit(app.exec())
|
||||
188
view/widgets/task_widget.py
Normal file
188
view/widgets/task_widget.py
Normal file
@ -0,0 +1,188 @@
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QPushButton, QMessageBox, QApplication)
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QPainter, QPixmap, QFont
|
||||
import sys
|
||||
|
||||
import resources.resources_rc
|
||||
|
||||
# 任务控件,如:管片任务、派单任务
|
||||
class TaskWidget(QWidget):
|
||||
def __init__(self, taskTitle:str, parent=None):
|
||||
super().__init__(parent)
|
||||
# 设置Widget大小与背景图一致
|
||||
self.bg_pixmap = QPixmap(":/icons/images/任务信息背景1.png")
|
||||
self.setFixedSize(self.bg_pixmap.size())
|
||||
|
||||
# 主布局(垂直)
|
||||
self.main_layout = QVBoxLayout(self)
|
||||
self.main_layout.setContentsMargins(0, 0, 0, 6)
|
||||
self.main_layout.setSpacing(0)
|
||||
|
||||
# 任务标题
|
||||
title_label = QLabel(taskTitle, self)
|
||||
title_label.setStyleSheet("font-size: 24px; color: #16ffff;padding-top:4px;")
|
||||
title_label.setAlignment(Qt.AlignCenter)
|
||||
self.main_layout.addWidget(title_label, alignment=Qt.AlignTop)
|
||||
|
||||
# 标题字体设置
|
||||
# title_font = title_label.font()
|
||||
title_font = QFont("Microsoft YaHei")
|
||||
title_font.setLetterSpacing(QFont.AbsoluteSpacing, 3) # 字间距3px
|
||||
title_font.setWeight(QFont.DemiBold)
|
||||
title_label.setFont(title_font)
|
||||
|
||||
# 用字典存储每个任务的可修改控件(键:任务名,值:控件字典)
|
||||
self.task_controls = {} # 结构:{"task1": {"volume_label": xxx, "time_label": xxx, ...}, ...}
|
||||
|
||||
# 三条任务条目
|
||||
self._add_task("task1", "SHRB1-3", ":/icons/images/任务矩形1.png")
|
||||
self._add_task("task2", "SHRB2-3", ":/icons/images/任务矩形2.png")
|
||||
self._add_task("task3", "SHRB1-3", ":/icons/images/任务矩形3.png")
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""绘制背景图片"""
|
||||
painter = QPainter(self)
|
||||
painter.drawPixmap(self.rect(), self.bg_pixmap)
|
||||
super().paintEvent(event)
|
||||
|
||||
def _add_task(self, task_name, task_id, status_icon):
|
||||
"添加相应的任务条目到布局,同时将其相应的控件存入字典"
|
||||
# 1、创建任务条目,以及相应的控件字典
|
||||
item_widget, controls = self._create_task_item(task_name, task_id, status_icon)
|
||||
# 2、将控件字典存入 相应的 任务条目字典
|
||||
self.task_controls[task_name] = controls
|
||||
# 3、将任务条目添加到主布局
|
||||
self.main_layout.addWidget(item_widget, alignment=Qt.AlignTop)
|
||||
|
||||
def _create_task_item(self, task_name, task_id, status_icon):
|
||||
"""创建单条任务条目, 返回任务条目和控件字典"""
|
||||
item_widget = QWidget()
|
||||
item_layout = QVBoxLayout(item_widget)
|
||||
item_layout.setContentsMargins(9, 6, 9, 8)
|
||||
item_layout.setSpacing(0)
|
||||
|
||||
# 相应的 任务条目的控件字典
|
||||
controls = {} # {"volume_label": ..., "time_label": ..., ...}
|
||||
|
||||
# 水平布局1:选择按钮 + 任务名 + 详情按钮
|
||||
row1_layout = QHBoxLayout()
|
||||
# 任务选择按钮
|
||||
select_btn = QPushButton()
|
||||
select_btn.setFixedSize(14, 14)
|
||||
select_btn.setCursor(Qt.PointingHandCursor)
|
||||
select_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-image: url(:/icons/images/任务信息选择按钮1.png);
|
||||
border: none;
|
||||
}
|
||||
QPushButton:checked {
|
||||
background-image: url(:/icons/images/任务信息选择按钮2.png);
|
||||
}
|
||||
""")
|
||||
select_btn.setCheckable(True)
|
||||
controls["select_btn"] = select_btn
|
||||
row1_layout.addWidget(select_btn)
|
||||
|
||||
# 任务编号
|
||||
task_id_label = QLabel(task_id)
|
||||
task_id_label.setStyleSheet("font-size: 18px; color: #16ffff;padding-left: 6px;")
|
||||
controls["task_id_label"] = task_id_label
|
||||
row1_layout.addWidget(task_id_label)
|
||||
|
||||
# 详情按钮
|
||||
detail_btn = QPushButton()
|
||||
detail_btn.setText("详情")
|
||||
detail_btn.setFixedSize(46, 26)
|
||||
detail_btn.setCursor(Qt.PointingHandCursor)
|
||||
detail_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-image: url(:/icons/images/任务信息详情按钮1.png);
|
||||
border: none;
|
||||
color: #3bfff8;
|
||||
font-size: 16px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-image: url(:/icons/images/任务信息详情按钮2.png);
|
||||
color: #001c83;
|
||||
font-size: 16px;
|
||||
}
|
||||
""")
|
||||
detail_btn.clicked.connect(lambda: self._show_detail_dialog(task_name)) # 详情按钮槽函数
|
||||
controls["detail_btn"] = detail_btn
|
||||
row1_layout.addWidget(detail_btn)
|
||||
|
||||
item_layout.addLayout(row1_layout)
|
||||
|
||||
# 水平布局2:方量 + / + 时间 + 状态图标
|
||||
row2_layout = QHBoxLayout()
|
||||
# 方量标签
|
||||
volume_label = QLabel("方量 200")
|
||||
volume_label.setStyleSheet("color: #a1c1d7; font-size: 14px;padding-left: 19px;")
|
||||
controls["volume_label"] = volume_label
|
||||
row2_layout.addWidget(volume_label)
|
||||
|
||||
# 分隔标签
|
||||
sep_label = QLabel("/")
|
||||
sep_label.setStyleSheet("color: #a1c1d7;")
|
||||
row2_layout.addWidget(sep_label, alignment=Qt.AlignCenter)
|
||||
|
||||
# 时间标签
|
||||
time_label = QLabel("03:22PM")
|
||||
time_label.setStyleSheet("color: #a1c1d7; font-size: 14px;")
|
||||
controls["time_label"] = time_label
|
||||
row2_layout.addWidget(time_label)
|
||||
|
||||
# 状态标签
|
||||
status_icon_label = QLabel()
|
||||
status_icon_label.setPixmap(QPixmap(status_icon))
|
||||
controls["status_icon_label"] = status_icon_label
|
||||
row2_layout.addWidget(status_icon_label, alignment=Qt.AlignRight)
|
||||
|
||||
item_layout.addLayout(row2_layout)
|
||||
|
||||
# 分隔线
|
||||
item_layout.setSpacing(5)
|
||||
separator = QLabel()
|
||||
separator.setPixmap(QPixmap(":/icons/images/任务信息分隔.png"))
|
||||
separator.setFixedSize(196, 1)
|
||||
item_layout.addWidget(separator)
|
||||
|
||||
return item_widget, controls # 返回任务条目 以及 相应的控件
|
||||
|
||||
def _show_detail_dialog(self, task_name):
|
||||
"""显示任务详情弹窗"""
|
||||
QMessageBox.information(self, "任务详情", f"任务 {task_name} 的详细信息...")
|
||||
|
||||
|
||||
# --------------------------
|
||||
# 对外接口:修改任务属性
|
||||
# 三个任务条目对应的任务名task_name,分别为 task1、task2、task3
|
||||
# --------------------------
|
||||
def set_task_volume(self, task_name:str, volume: float):
|
||||
"""修改指定任务的方量, 传入具体的方量值,如: 200.0"""
|
||||
if task_name in self.task_controls:
|
||||
volume_label = self.task_controls[task_name]["volume_label"]
|
||||
volume_label.setText(f"方量 {volume}")
|
||||
|
||||
def set_task_time(self, task_name:str, time_str: str):
|
||||
"""修改指定任务的时间, 传入对应格式的时间,如: 03:22PM"""
|
||||
if task_name in self.task_controls:
|
||||
time_label = self.task_controls[task_name]["time_label"]
|
||||
time_label.setText(time_str)
|
||||
|
||||
def set_task_id(self, task_name:str, new_id: str):
|
||||
"""修改指定任务的编号, 如: SHRB2-3"""
|
||||
if task_name in self.task_controls:
|
||||
task_id_label = self.task_controls[task_name]["task_id_label"]
|
||||
task_id_label.setText(new_id)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
widget = TaskWidget("管片任务")
|
||||
# 示例:修改task2的方量为300(测试用)
|
||||
widget.set_task_volume("task2", 300)
|
||||
# 示例:修改task1的时间为04:50PM(测试用)
|
||||
widget.set_task_time("task1", "04:50PM")
|
||||
widget.show()
|
||||
sys.exit(app.exec())
|
||||
145
view/widgets/value_adjuster.py
Normal file
145
view/widgets/value_adjuster.py
Normal file
@ -0,0 +1,145 @@
|
||||
from PySide6.QtWidgets import (QWidget, QHBoxLayout, QPushButton, QLineEdit,
|
||||
QApplication)
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QDoubleValidator
|
||||
import sys
|
||||
|
||||
# 调整计划方量
|
||||
class ValueAdjuster(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.min_value = 0 # 最小值
|
||||
self.max_value = 99 # 最大值
|
||||
self.value = 2.5 # 初始值
|
||||
|
||||
self.setFixedSize(102, 32)
|
||||
|
||||
# 创建控件
|
||||
# 减号按钮
|
||||
self.minus_btn = QPushButton("-")
|
||||
self.minus_btn.setFixedSize(26, 26)
|
||||
self.minus_btn.setCursor(Qt.PointingHandCursor)
|
||||
|
||||
# 中间的编辑栏
|
||||
self.line_edit = QLineEdit(f"{self.value:.1f}") # 显示1位小数
|
||||
self.line_edit.setFixedSize(40, 26)
|
||||
|
||||
# 加号按钮
|
||||
self.plus_btn = QPushButton("+")
|
||||
self.plus_btn.setFixedSize(26, 26)
|
||||
self.plus_btn.setCursor(Qt.PointingHandCursor)
|
||||
|
||||
# 配置QLineEdit:支持数字输入+居中显示
|
||||
self.line_edit.setAlignment(Qt.AlignCenter) # 文本居中
|
||||
# 限制输入为浮点数(支持负数,范围可自定义)
|
||||
self.line_edit.setValidator(QDoubleValidator(0, 99, 1, self)) # 最多1位小数
|
||||
self.line_edit.textChanged.connect(self.on_text_changed) # 监听输入变化
|
||||
|
||||
# 设置样式表(保持与按钮风格统一)
|
||||
self.minus_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #001c83;
|
||||
color: #03f5ff;
|
||||
border: 1px solid #039ec3;
|
||||
font-size: 29px;
|
||||
text-align: center;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #16ffff;
|
||||
color: #00347e;
|
||||
}
|
||||
""")
|
||||
|
||||
# #03f5ff
|
||||
self.line_edit.setStyleSheet("""
|
||||
QLineEdit {
|
||||
background-color: #001c83;
|
||||
color: white;
|
||||
border: 1px solid #039ec3;
|
||||
font-size: 16px;
|
||||
padding:0px;
|
||||
font-weight: 560;
|
||||
}
|
||||
QLineEdit:focus {
|
||||
border: 1px solid #039ec3;
|
||||
outline: none;
|
||||
}
|
||||
""")
|
||||
|
||||
self.plus_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #001c83;
|
||||
color: #03f5ff;
|
||||
border: 1px solid #039ec3;
|
||||
font-size: 29px;
|
||||
text-align: center;
|
||||
padding-bottom: 4px;
|
||||
margin: 0px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #16ffff;
|
||||
color: #00347e;
|
||||
}
|
||||
""")
|
||||
|
||||
# 连接信号槽(加减按钮)
|
||||
self.minus_btn.clicked.connect(self.on_minus_clicked)
|
||||
self.plus_btn.clicked.connect(self.on_plus_clicked)
|
||||
|
||||
# 设置布局(间距0,确保边框无缝衔接)
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setSpacing(2)
|
||||
layout.setContentsMargins(0, 0, 0, 0) # 去除布局外边距
|
||||
layout.addWidget(self.minus_btn)
|
||||
layout.addWidget(self.line_edit)
|
||||
layout.addWidget(self.plus_btn)
|
||||
|
||||
def on_minus_clicked(self):
|
||||
"""减0.1"""
|
||||
new_value = self.value - 0.1
|
||||
# 限制最小值:不低于min_value
|
||||
if new_value >= self.min_value:
|
||||
self.value = round(new_value, 1)
|
||||
self.line_edit.setText(f"{self.value:.1f}")
|
||||
|
||||
def on_plus_clicked(self):
|
||||
"""加0.1"""
|
||||
new_value = self.value + 0.1
|
||||
# 限制最大值:不超过max_value
|
||||
if new_value <= self.max_value:
|
||||
self.value = round(new_value, 1)
|
||||
self.line_edit.setText(f"{self.value:.1f}")
|
||||
|
||||
def on_text_changed(self, text):
|
||||
"""监听输入框文本变化,更新内部value"""
|
||||
if not text:
|
||||
return
|
||||
try:
|
||||
input_value = round(float(text), 1)
|
||||
# 确保输入值在范围内
|
||||
if self.min_value <= input_value <= self.max_value:
|
||||
self.value = input_value
|
||||
else:
|
||||
# 超出范围时恢复到最近的有效 value
|
||||
self.line_edit.setText(f"{self.value:.1f}")
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# 获取具体的方量数值
|
||||
def get_value(self):
|
||||
return self.value
|
||||
|
||||
# 设置方量值
|
||||
def set_value(self, value:float):
|
||||
input_value = round(float(value), 1)
|
||||
# 确保设置的值在范围内
|
||||
if self.min_value <= input_value <= self.max_value:
|
||||
self.value = input_value
|
||||
self.line_edit.setText(f"{self.value:.1f}") # 更新方量值
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
widget = ValueAdjuster()
|
||||
widget.show()
|
||||
sys.exit(app.exec())
|
||||
@ -1,7 +1,17 @@
|
||||
# coding:utf-8
|
||||
from PySide6.QtCore import Qt, QTimer, Signal, QObject
|
||||
from PySide6.QtGui import QImage, QPixmap
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QApplication
|
||||
from PySide6.QtGui import QImage, QPixmap, QPalette, QBrush, QCursor, QIcon, QPainter, QFont
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QApplication,
|
||||
QFrame,
|
||||
QPushButton,
|
||||
QStackedWidget,
|
||||
QSizePolicy
|
||||
)
|
||||
from typing import Optional
|
||||
|
||||
import cv2
|
||||
@ -9,12 +19,23 @@ import time
|
||||
from threading import Thread, Lock
|
||||
import sys
|
||||
|
||||
from .switch_button import SwitchButton
|
||||
|
||||
# ====================== 后台视频流管理(自动重连)======================
|
||||
class VideoStream:
|
||||
"""后台读取 RTSP 流,自动重连,只返回新鲜帧"""
|
||||
import resources.resources_rc
|
||||
|
||||
def __init__(self, rtsp_url, name="Stream"):
|
||||
|
||||
class VideoStream(QObject):
|
||||
# 系统(信息)信号
|
||||
info_signal = Signal(str, str)
|
||||
|
||||
# 警告信号(重试): 发送摄像头名称和状态描述
|
||||
warning_signal = Signal(str, str)
|
||||
|
||||
# 异常错误信号(超次数)
|
||||
error_signal = Signal(str, str)
|
||||
|
||||
def __init__(self, rtsp_url, name="Stream", max_retry=3):
|
||||
super().__init__()
|
||||
self.rtsp_url = rtsp_url
|
||||
self.name = name
|
||||
self.cap = None
|
||||
@ -24,40 +45,103 @@ class VideoStream:
|
||||
self.running = False
|
||||
self.thread = None
|
||||
|
||||
self.max_retry = max_retry
|
||||
self.retry_count = 0
|
||||
self.manual_stop = False # 是否停止自动重连
|
||||
|
||||
def start(self):
|
||||
self.running = True
|
||||
self.thread = Thread(target=self.update, args=(), daemon=True)
|
||||
self.thread = Thread(target=self.update, daemon=True)
|
||||
self.thread.start()
|
||||
return self
|
||||
|
||||
def reset_retry(self):
|
||||
with self.lock:
|
||||
self.retry_count = 0
|
||||
self.manual_stop = False
|
||||
if self.cap:
|
||||
self.cap.release()
|
||||
self.cap = None
|
||||
self.info_signal.emit(self.name, "已手动重置, 开始尝试连接...")
|
||||
|
||||
def _handle_max_retry(self, failure_type: str):
|
||||
"""
|
||||
处理超过最大重试次数的情况
|
||||
failure_type: 失败类型("连接" 或 "读取")
|
||||
"""
|
||||
self.manual_stop = True
|
||||
error_msg = f"{failure_type}摄像头失败, 已经超过最大次数({self.max_retry}次),可能设备故障,请检查后手动点击重连"
|
||||
print(f"[{self.name}] {error_msg}")
|
||||
self.error_signal.emit(self.name, error_msg)
|
||||
time.sleep(1)
|
||||
|
||||
def _handle_retry_delay(self):
|
||||
"""计算并执行重试前的延迟(递增间隔)"""
|
||||
sleep_time = min(0.5 + self.retry_count * 0.5, 5)
|
||||
warning_msg = (
|
||||
f"第{self.retry_count}次连接失败,将在{sleep_time:.1f}秒后再次尝试..."
|
||||
)
|
||||
self.warning_signal.emit(self.name, warning_msg)
|
||||
print(f"[{self.name}] {warning_msg}")
|
||||
time.sleep(sleep_time)
|
||||
|
||||
def update(self):
|
||||
while self.running:
|
||||
if self.cap is None or not self.cap.isOpened():
|
||||
print(f"[{self.name}] 正在连接 RTSP: {self.rtsp_url}")
|
||||
try:
|
||||
self.cap = cv2.VideoCapture(self.rtsp_url, cv2.CAP_FFMPEG)
|
||||
if hasattr(cv2, 'CAP_PROP_BUFFERSIZE'):
|
||||
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
|
||||
if hasattr(cv2, 'CAP_PROP_READ_TIMEOUT'):
|
||||
self.cap.set(cv2.CAP_PROP_READ_TIMEOUT, 2000)
|
||||
if hasattr(cv2, 'CAP_PROP_TCP_NODELAY'):
|
||||
self.cap.set(cv2.CAP_PROP_TCP_NODELAY, 1)
|
||||
except Exception as e:
|
||||
print(f"[{self.name}] 连接失败: {e}")
|
||||
if self.manual_stop:
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
if self.cap is None or not self.cap.isOpened():
|
||||
print(
|
||||
f"[{self.name}]正在连接 RTSP(第{self.retry_count+1}次): {self.rtsp_url}"
|
||||
)
|
||||
info_msg = f"正在连接 RTSP(第{self.retry_count+1}次): {self.rtsp_url}"
|
||||
self.info_signal.emit(self.name, info_msg)
|
||||
try:
|
||||
self.cap = cv2.VideoCapture(self.rtsp_url, cv2.CAP_FFMPEG)
|
||||
if hasattr(cv2, "CAP_PROP_BUFFERSIZE"):
|
||||
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 2)
|
||||
if hasattr(cv2, "CAP_PROP_READ_TIMEOUT"):
|
||||
self.cap.set(cv2.CAP_PROP_READ_TIMEOUT, 2000)
|
||||
if hasattr(cv2, "CAP_PROP_TCP_NODELAY"):
|
||||
self.cap.set(cv2.CAP_PROP_TCP_NODELAY, 1)
|
||||
except Exception as ex:
|
||||
print(f"[{self.name}]连接失败: {ex}")
|
||||
warning_msg = f"连接失败: {str(ex)},准备重试..."
|
||||
self.warning_signal.emit(self.name, warning_msg)
|
||||
if self.cap:
|
||||
self.cap.release()
|
||||
self.cap = None
|
||||
|
||||
if self.cap is None or not self.cap.isOpened():
|
||||
self.retry_count += 1
|
||||
if self.retry_count >= self.max_retry:
|
||||
self._handle_max_retry("连接")
|
||||
continue
|
||||
self._handle_retry_delay()
|
||||
continue
|
||||
else:
|
||||
self.retry_count = 0
|
||||
self.info_signal.emit(self.name, "连接成功")
|
||||
|
||||
ret, frame = self.cap.read()
|
||||
if ret:
|
||||
with self.lock:
|
||||
self.frame = frame.copy()
|
||||
self.timestamp = time.time()
|
||||
self.retry_count = 0
|
||||
else:
|
||||
print(f"[{self.name}] 读取失败,准备重连...")
|
||||
warning_msg = "读取帧失败,准备重连..."
|
||||
self.warning_signal.emit(self.name, warning_msg)
|
||||
if self.cap:
|
||||
self.cap.release()
|
||||
self.cap = None
|
||||
time.sleep(1)
|
||||
self.retry_count += 1
|
||||
if self.retry_count >= self.max_retry:
|
||||
self._handle_max_retry("读取")
|
||||
continue
|
||||
self._handle_retry_delay()
|
||||
|
||||
def read(self):
|
||||
with self.lock:
|
||||
@ -75,102 +159,307 @@ class VideoStream:
|
||||
print(f"[{self.name}] 视频流已停止")
|
||||
|
||||
|
||||
# ====================== 摄像头功能模块 ======================
|
||||
# ====================== 摄像头框功能模块 ======================
|
||||
class CameraModule(QWidget):
|
||||
"""单个摄像头模块:原图显示"""
|
||||
|
||||
def __init__(self, camera_name="摄像头", rtsp_url="", need_rotate_180=True, parent=None):
|
||||
# 重置信号,用于通知需要重置连接 (重连)
|
||||
reset_signal = Signal()
|
||||
|
||||
# 视频显示信号,用于通知是否显示视频(刷新视频帧)
|
||||
# 发送摄像头名 和 是否显示
|
||||
video_display_signal = Signal(str, bool)
|
||||
|
||||
def __init__(
|
||||
self, camera_name="摄像头", rtsp_url="", need_rotate_180=True, parent=None
|
||||
):
|
||||
super().__init__(parent)
|
||||
self.setObjectName("cameraModule")
|
||||
self.camera_name = camera_name
|
||||
self.rtsp_url = rtsp_url
|
||||
self.need_rotate_180 = need_rotate_180 # 画面是否需要旋转180度后显示
|
||||
self.need_rotate_180 = need_rotate_180 # 画面是否需要旋转180度后显示
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(0)
|
||||
# layout.setContentsMargins(8, 8, 8, 8)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# --- 0. 标题 ---
|
||||
# --- 0. 标题布局: 标题 + 显示标签及开关 ---
|
||||
title_layout = QHBoxLayout()
|
||||
title_layout.setContentsMargins(0, 0, 0, 4)
|
||||
title_layout.setSpacing(0)
|
||||
|
||||
self.title_label = QLabel()
|
||||
self.title_label.setFixedSize(106, 25)
|
||||
self.title_label.setAlignment(Qt.AlignLeft)
|
||||
self.title_label.setText(f"{self.camera_name}视频")
|
||||
self.title_label.setObjectName("cameraTitleLabel")
|
||||
self.title_label.setStyleSheet("""
|
||||
self.title_label.setStyleSheet(
|
||||
"""
|
||||
#cameraTitleLabel {
|
||||
font-size: 16px;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
color: #16ffff;
|
||||
background-image: url(":/icons/images/视频标题背景.png");
|
||||
min-width: 180px;
|
||||
padding-left: 12px;
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
# 创建【显示标签+开关】组合容器
|
||||
self.display_group = QWidget() # 容器:包裹标签和开关
|
||||
display_group_layout = QHBoxLayout(self.display_group)
|
||||
display_group_layout.setContentsMargins(0, 0, 0, 0) # 容器内边距为0
|
||||
display_group_layout.setSpacing(0)
|
||||
|
||||
# 显示标签
|
||||
self.display_label = QLabel("显示")
|
||||
self.display_label.setObjectName("displayLabel")
|
||||
# font-weight: bold;
|
||||
self.display_label.setStyleSheet("""
|
||||
#displayLabel {
|
||||
font-size: 18px;
|
||||
color: #16ffff;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
""")
|
||||
# 让标签垂直居中
|
||||
self.display_label.setFixedWidth(40)
|
||||
self.display_label.setAlignment(Qt.AlignVCenter | Qt.AlignLeft)
|
||||
|
||||
# --- 1. 原始图像 ---
|
||||
self.raw_label = QLabel() # 显示图像的 label
|
||||
# self.raw_label.setFixedSize(320, 240)
|
||||
self.raw_label.setFixedSize(327, 199) # 显示的图像的宽、高
|
||||
# 显示开关
|
||||
self.display_switch = SwitchButton()
|
||||
self.display_switch.setChecked(True)
|
||||
self.display_switch.switched.connect(self.onDisplayButtonSwitched)
|
||||
|
||||
palette = self.raw_label.palette()
|
||||
palette.setColor(self.raw_label.foregroundRole(), Qt.white) # 字体颜色:白色
|
||||
self.raw_label.setPalette(palette)
|
||||
# 将显示标签和开关添加到组合容器布局
|
||||
display_group_layout.addWidget(self.display_label)
|
||||
display_group_layout.addWidget(self.display_switch, alignment=Qt.AlignLeft)
|
||||
|
||||
# 添加到标题布局
|
||||
title_layout.addWidget(self.title_label, alignment=Qt.AlignLeft)
|
||||
title_layout.addWidget(self.display_group, alignment=Qt.AlignLeft)
|
||||
|
||||
# 视频显示容器:使用堆叠布局,让重连按钮和视频标签分层显示
|
||||
self.video_container = QWidget()
|
||||
self.video_container.setObjectName("videoContainer")
|
||||
self.video_container.setFixedSize(327, 199) # 需要同步修改下面的尺寸
|
||||
# #011d6b #033474
|
||||
# self.video_container.setStyleSheet(
|
||||
# "background-color: #011d6b; border: none;"
|
||||
# ) # 明确视频显示背景色
|
||||
self.video_container.setStyleSheet(
|
||||
"""
|
||||
#videoContainer {
|
||||
border-image: url(":/icons/images/视频框背景.png");
|
||||
}
|
||||
"""
|
||||
)
|
||||
video_layout = QVBoxLayout(self.video_container)
|
||||
video_layout.setContentsMargins(6, 6, 5, 6)
|
||||
|
||||
# 原始图像显示
|
||||
self.raw_label = QLabel()
|
||||
self.raw_label.setAlignment(Qt.AlignCenter)
|
||||
# self.raw_label.setStyleSheet("background: black; border: 1px solid #ccc;")
|
||||
self.raw_label.setStyleSheet("background: #033474; border: none") # 视频显示框样式
|
||||
self.raw_label.setStyleSheet("border: none;") # 移除背景,使用容器背景
|
||||
# self.raw_label.setStyleSheet("background-color: red;")
|
||||
self.raw_label.setText(f"{self.camera_name}摄像头, 连接中...")
|
||||
# self.raw_label.setFixedSize(327, 199)
|
||||
# self.raw_label.setFixedSize(314, 187) # 需要根据视频框背景大小调整 !!!
|
||||
self.raw_label.setFixedSize(316, 187) # 需要根据视频框背景大小调整 !!!
|
||||
|
||||
# --- 添加到主布局 ---
|
||||
layout.addWidget(self.title_label) # 标题
|
||||
layout.addWidget(self.raw_label) # 图像
|
||||
# 重置按钮:居中显示、透明背景
|
||||
self.reset_button = QPushButton()
|
||||
# self.reset_button.setFixedSize(327, 199)
|
||||
# self.reset_button.setFixedSize(314, 187) # 同上
|
||||
self.reset_button.setFixedSize(316, 187) # 同上
|
||||
self.reset_button.setStyleSheet(
|
||||
"""
|
||||
QPushButton {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding: 0px;
|
||||
}
|
||||
"""
|
||||
)
|
||||
self.reset_button.setCursor(QCursor(Qt.PointingHandCursor))
|
||||
self.reset_button.clicked.connect(self.onRestButtonClicked)
|
||||
|
||||
# 加载视频重新连接图片
|
||||
try:
|
||||
pixmap = QPixmap(":/icons/images/界面刷新.png")
|
||||
if not pixmap.isNull():
|
||||
self.reset_button.setIcon(QIcon(pixmap))
|
||||
self.reset_button.setIconSize(pixmap.size())
|
||||
else:
|
||||
# 图片加载失败(文件不存在或格式错误)
|
||||
self.reset_button.setText("点击重置连接")
|
||||
except Exception as e:
|
||||
print(f"加载复位图片失败: {e}")
|
||||
self.reset_button.setText("点击重置连接")
|
||||
|
||||
# 堆叠布局:重连按钮和视频标签分层
|
||||
self.stacked_widget = QStackedWidget()
|
||||
self.stacked_widget.addWidget(self.raw_label)
|
||||
self.stacked_widget.addWidget(self.reset_button)
|
||||
video_layout.addWidget(self.stacked_widget, alignment=Qt.AlignCenter)
|
||||
|
||||
# 添加到主布局
|
||||
layout.addLayout(title_layout)
|
||||
layout.addWidget(self.video_container)
|
||||
|
||||
# 显示开关切换槽函数
|
||||
def onDisplayButtonSwitched(self, state:bool):
|
||||
# 显示开关打开,state 为 True,显示开关关闭,state 为 False
|
||||
if state:
|
||||
self.stacked_widget.setCurrentWidget(self.raw_label) # 视频显示标签
|
||||
self.stacked_widget.setHidden(False)
|
||||
else:
|
||||
self.stacked_widget.setHidden(True)
|
||||
self.video_display_signal.emit(self.camera_name, state)
|
||||
|
||||
# 重连按钮点击槽函数
|
||||
def onRestButtonClicked(self):
|
||||
self.stacked_widget.setCurrentWidget(self.raw_label)
|
||||
self.raw_label.setText(f"{self.camera_name}摄像头, 连接中...")
|
||||
self.reset_signal.emit() # 发送重连信号
|
||||
|
||||
def showResetButton(self):
|
||||
self.raw_label.setText("")
|
||||
self.stacked_widget.setCurrentWidget(self.reset_button)
|
||||
|
||||
def update_raw_image(self, image: Optional[QImage]):
|
||||
if image:
|
||||
pixmap = QPixmap.fromImage(image)
|
||||
self.raw_label.setPixmap(pixmap.scaled(
|
||||
self.raw_label.size(),
|
||||
Qt.KeepAspectRatio,
|
||||
Qt.SmoothTransformation
|
||||
))
|
||||
self.raw_label.setPixmap(
|
||||
pixmap.scaled(
|
||||
self.raw_label.size(),
|
||||
Qt.IgnoreAspectRatio, # 忽略比例,占满窗口 可选:Qt.KeepAspectRatio
|
||||
Qt.SmoothTransformation,
|
||||
)
|
||||
)
|
||||
self.raw_label.setText("")
|
||||
else:
|
||||
self.raw_label.setPixmap(QPixmap())
|
||||
self.raw_label.setText(f"{self.camera_name}摄像头, 断线中...")
|
||||
# 没有图片数据,stream流会自动重连 或 手动重连
|
||||
self.raw_label.setText(f"{self.camera_name}摄像头, 连接中...")
|
||||
|
||||
# 视频显示区域 总的标题 (如 振捣视频)
|
||||
class VideoTitleWidget(QWidget):
|
||||
def __init__(self, video_name, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setStyleSheet("background-color: transparent;") # 整体透明
|
||||
self.setup_ui(video_name)
|
||||
|
||||
def setup_ui(self, video_name):
|
||||
# 水平布局:文字 + 图标
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0) # 布局内边距为0
|
||||
|
||||
# 文字标签
|
||||
self.text_label = QLabel(f"{video_name}视频")
|
||||
self.text_label.setStyleSheet("""
|
||||
font-family: "微软雅黑";
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #16FFFF;
|
||||
background-color: transparent;
|
||||
""")
|
||||
font = self.text_label.font()
|
||||
font.setLetterSpacing(QFont.SpacingType.AbsoluteSpacing, 3) # 字间距3px(可按需加大)
|
||||
self.text_label.setFont(font)
|
||||
|
||||
# 图标标签
|
||||
self.icon_label = QLabel()
|
||||
self.icon_label.setPixmap(QPixmap(":/icons/images/视频摄像头标志.png")) # 需要换为实际的图片的路径
|
||||
self.icon_label.setStyleSheet("background-color: transparent;")
|
||||
self.icon_label.setFixedSize(23, 16)
|
||||
self.icon_label.setScaledContents(True)
|
||||
|
||||
# 添加到布局
|
||||
layout.addWidget(self.text_label, alignment=Qt.AlignCenter)
|
||||
layout.addWidget(self.icon_label, alignment=Qt.AlignCenter)
|
||||
|
||||
|
||||
class VibrationVideoWidget(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self.setObjectName("vibrationVideoWidget")
|
||||
|
||||
# 1. 加载背景图片(替换为你的图片路径)
|
||||
self.background_image = QImage() # 初始化图片对象
|
||||
# 注意:图片路径需正确,建议使用绝对路径或项目相对路径
|
||||
self.background_path = ":/icons/images/视频背景.png" # 你的背景图片路径
|
||||
if not self.background_image.load(self.background_path):
|
||||
# 图片加载失败时的容错处理(显示红色背景作为提示)
|
||||
print(f"警告:无法加载背景图片 {self.background_path}")
|
||||
self.background_image = QImage(388, 839, QImage.Format_RGB32)
|
||||
self.background_image.fill(Qt.GlobalColor.red) # 加载失败时用红色填充
|
||||
|
||||
# 显示摄像头画面的模组
|
||||
# 需要修改为相应的地址!!!
|
||||
self.cam1 = CameraModule("上位料斗", "rtsp://admin:XJ123456@192.168.1.50:554/streaming/channels/101")
|
||||
self.cam2 = CameraModule("下位料斗", "rtsp://admin:XJ123456@192.168.1.51:554/streaming/channels/101")
|
||||
self.cam3 = CameraModule("模具车", "rtsp://admin:XJ123456@192.168.1.51:554/streaming/channels/101")
|
||||
# 注:在camera_controller中设置地址url
|
||||
self.cam1 = CameraModule("上位料斗")
|
||||
self.cam2 = CameraModule("下位料斗")
|
||||
self.cam3 = CameraModule("模具车")
|
||||
|
||||
self.setup_ui()
|
||||
self.init_streams()
|
||||
self.connect_signals()
|
||||
|
||||
def set_camera_urls(self, cam1_url, cam2_url, cam3_url):
|
||||
"""设置三个摄像头的RTSP URL
|
||||
注意: 在CameraController中调用
|
||||
"""
|
||||
self.cam1.rtsp_url = cam1_url
|
||||
self.cam2.rtsp_url = cam2_url
|
||||
self.cam3.rtsp_url = cam3_url
|
||||
|
||||
def setup_ui(self):
|
||||
# 视频widget样式
|
||||
self.setFixedSize(387, 720) # 宽387、高792
|
||||
self.setAutoFillBackground(True)
|
||||
self.setStyleSheet("""
|
||||
#vibrationVideoWidget {
|
||||
background-color: #043d76;
|
||||
border: none; /* 可选:去除默认边框,避免视觉干扰 */
|
||||
}
|
||||
""")
|
||||
# self.setFixedSize(387, 720) # 宽387、高792
|
||||
self.setFixedSize(388, 839) # 背景图片宽388、高879
|
||||
|
||||
# 1、总的标题
|
||||
self.title_widget = VideoTitleWidget("振捣")
|
||||
|
||||
|
||||
# 布局设置
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(16, 16, 16, 16)
|
||||
layout.setSpacing(6) # 每两个摄像头模块间隔6px
|
||||
layout.addWidget(self.cam1)
|
||||
layout.addWidget(self.cam2)
|
||||
layout.addWidget(self.cam3)
|
||||
# 2. 创建摄像头容器(专门存放cam1/cam2/cam3)
|
||||
self.camera_container = QWidget() # 容器widget
|
||||
self.camera_container.setFixedSize(350, 720) # 容器固定尺寸
|
||||
# 给容器添加样式
|
||||
self.camera_container.setStyleSheet("""
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
""")
|
||||
|
||||
# 3. 容器内部布局(管理三个摄像头模块)
|
||||
container_layout = QVBoxLayout(self.camera_container)
|
||||
container_layout.setContentsMargins(6, 6, 6, 6) # 容器内边距
|
||||
container_layout.setSpacing(6) # 摄像头模块之间的间距
|
||||
|
||||
# 将三个摄像头添加到容器布局中
|
||||
container_layout.addWidget(self.cam1, alignment=Qt.AlignCenter)
|
||||
container_layout.addWidget(self.cam2, alignment=Qt.AlignCenter)
|
||||
container_layout.addWidget(self.cam3, alignment=Qt.AlignCenter)
|
||||
|
||||
# 4. 主窗口布局(管理摄像头容器等其他部件)
|
||||
main_layout = QVBoxLayout(self) # 主窗口的布局
|
||||
main_layout.setContentsMargins(0, 0, 9, 30) # 主布局内边距为0
|
||||
|
||||
# 添加标题部件
|
||||
main_layout.addWidget(self.title_widget, alignment=Qt.AlignCenter) # 标题居中
|
||||
|
||||
# 添加43px间距(标题与摄像头容器之间)
|
||||
main_layout.addSpacing(16)
|
||||
|
||||
# 把摄像头容器添加到主布局中(作为一个整体)
|
||||
main_layout.addWidget(self.camera_container, alignment=Qt.AlignBottom | Qt.AlignHCenter)
|
||||
|
||||
|
||||
# 初始化视频流 (注意:设置好了camera_urls之后调用)
|
||||
def init_streams(self):
|
||||
url1 = self.cam1.rtsp_url
|
||||
url2 = self.cam2.rtsp_url
|
||||
@ -182,20 +471,55 @@ class VibrationVideoWidget(QWidget):
|
||||
|
||||
self.timer1 = QTimer()
|
||||
self.timer1.timeout.connect(self.update_frame1)
|
||||
|
||||
|
||||
self.timer2 = QTimer()
|
||||
self.timer2.timeout.connect(self.update_frame2)
|
||||
|
||||
|
||||
self.timer3 = QTimer()
|
||||
self.timer3.timeout.connect(self.update_frame3)
|
||||
|
||||
|
||||
# 定时读取视频流
|
||||
self.timer1.start(33)
|
||||
self.timer2.start(33)
|
||||
self.timer3.start(33)
|
||||
|
||||
def connect_signals(self):
|
||||
pass
|
||||
# 流Stream相关的槽函数连接放在了 camera_controller之中
|
||||
|
||||
# 视频显示相关的槽函数连接
|
||||
self.cam1.video_display_signal.connect(self.onVideoDisplay)
|
||||
self.cam2.video_display_signal.connect(self.onVideoDisplay)
|
||||
self.cam3.video_display_signal.connect(self.onVideoDisplay)
|
||||
|
||||
def onVideoDisplay(self, cam_name: str, state: bool):
|
||||
# 1. 摄像头与 stream、timer 的映射关系
|
||||
cam_mapping = {
|
||||
"上位料斗": (self.stream1, self.timer1),
|
||||
"下位料斗": (self.stream2, self.timer2),
|
||||
"模具车": (self.stream3, self.timer3)
|
||||
}
|
||||
|
||||
# 2. 获取当前摄像头对应的 stream 和 timer(处理无效名称的情况)
|
||||
stream, timer = cam_mapping.get(cam_name, (None, None))
|
||||
if not stream or not timer:
|
||||
print(f"警告:未知摄像头名称 {cam_name}")
|
||||
return
|
||||
|
||||
# 3. 根据 state 执行操作
|
||||
if state: # 显示视频
|
||||
stream.reset_retry() # 视频流重连
|
||||
timer.start(33) # 定时器开始,显示视频帧
|
||||
else: # 关闭视频
|
||||
timer.stop() # 定时器停止
|
||||
stream.manual_stop = True # 视频流停止读取
|
||||
|
||||
def onStreamError(self, stream_name):
|
||||
if stream_name == "Cam1":
|
||||
self.cam1.showResetButton()
|
||||
elif stream_name == "Cam2":
|
||||
self.cam2.showResetButton()
|
||||
elif stream_name == "Cam3":
|
||||
self.cam3.showResetButton()
|
||||
|
||||
def update_frame1(self):
|
||||
frame = self.stream1.read()
|
||||
@ -208,7 +532,7 @@ class VibrationVideoWidget(QWidget):
|
||||
self.cam1.update_raw_image(q_img)
|
||||
else:
|
||||
self.cam1.update_raw_image(None)
|
||||
|
||||
|
||||
def update_frame2(self):
|
||||
frame = self.stream2.read()
|
||||
if frame is not None:
|
||||
@ -233,19 +557,43 @@ class VibrationVideoWidget(QWidget):
|
||||
else:
|
||||
self.cam3.update_raw_image(None)
|
||||
|
||||
def paintEvent(self, event):
|
||||
# 1. 先调用父类的paintEvent
|
||||
super().paintEvent(event)
|
||||
|
||||
# 2. 创建画家对象,绘制背景图片
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform) # 图片缩放平滑
|
||||
|
||||
# 3. 获取部件的实际尺寸(这里是387x720,与setup_ui中一致)
|
||||
widget_rect = self.rect()
|
||||
|
||||
# 4. 绘制背景图片(缩放至部件大小)
|
||||
# 可选缩放策略:
|
||||
# - Qt.KeepAspectRatio:保持图片比例,可能有黑边
|
||||
# - Qt.IgnoreAspectRatio:拉伸填充,可能变形
|
||||
# - Qt.KeepAspectRatioByExpanding:按比例放大至覆盖部件,可能裁剪边缘
|
||||
scaled_pixmap = QPixmap.fromImage(self.background_image).scaled(
|
||||
widget_rect.size(),
|
||||
Qt.KeepAspectRatio, # 推荐:保持比例,避免变形
|
||||
Qt.SmoothTransformation # 平滑缩放
|
||||
)
|
||||
|
||||
# 5. 计算图片绘制位置(居中显示,若有黑边则均匀分布)
|
||||
x = (widget_rect.width() - scaled_pixmap.width()) // 2
|
||||
y = (widget_rect.height() - scaled_pixmap.height()) // 2
|
||||
painter.drawPixmap(x, y, scaled_pixmap)
|
||||
|
||||
# 6. 结束绘制
|
||||
painter.end()
|
||||
|
||||
# ====================== 清理资源 ======================
|
||||
def closeEvent(self, event):
|
||||
self.hide()
|
||||
self.stream1.stop()
|
||||
self.stream2.stop()
|
||||
self.stream3.stop()
|
||||
self.timer1.stop()
|
||||
self.timer2.stop()
|
||||
self.timer3.stop()
|
||||
self.stream1.stop()
|
||||
self.stream2.stop()
|
||||
self.stream3.stop()
|
||||
super().closeEvent(event)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication([])
|
||||
window = VibrationVideoWidget()
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
|
||||
Reference in New Issue
Block a user