Update GUI 2.2.0: Settings tab
This commit is contained in:
+121
-30
@@ -1,10 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QCheckBox)
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QCheckBox, QApplication)
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from PyQt6.QtGui import QPainter, QPen, QColor, QFont, QPolygonF
|
||||
from PyQt6.QtCore import QPointF, Qt
|
||||
import math
|
||||
|
||||
|
||||
def _get_font_scale():
|
||||
app = QApplication.instance()
|
||||
if app is None:
|
||||
return 1.0
|
||||
scale = app.property("font_scale")
|
||||
try:
|
||||
return float(scale) if scale is not None else 1.0
|
||||
except (TypeError, ValueError):
|
||||
return 1.0
|
||||
|
||||
|
||||
def _scale_stylesheet_font_sizes(stylesheet, scale):
|
||||
if not stylesheet or 'font-size' not in stylesheet:
|
||||
return stylesheet
|
||||
|
||||
import re
|
||||
|
||||
def repl(match):
|
||||
size = float(match.group(1))
|
||||
unit = match.group(2)
|
||||
scaled = max(1.0, size * scale)
|
||||
text = f"{scaled:.2f}".rstrip('0').rstrip('.')
|
||||
return f"font-size: {text}{unit}"
|
||||
|
||||
return re.sub(r'font-size\s*:\s*([0-9]+(?:\.[0-9]+)?)\s*(px|pt)', repl, stylesheet)
|
||||
|
||||
|
||||
def _set_scaled_stylesheet(widget, stylesheet):
|
||||
widget._base_stylesheet = stylesheet
|
||||
scaled = _scale_stylesheet_font_sizes(stylesheet, _get_font_scale())
|
||||
widget._applied_stylesheet = scaled
|
||||
widget.setStyleSheet(scaled)
|
||||
|
||||
|
||||
def _reapply_scaled_stylesheet(widget):
|
||||
current_stylesheet = widget.styleSheet()
|
||||
base_stylesheet = getattr(widget, '_base_stylesheet', None)
|
||||
applied_stylesheet = getattr(widget, '_applied_stylesheet', None)
|
||||
|
||||
if current_stylesheet != applied_stylesheet:
|
||||
base_stylesheet = current_stylesheet
|
||||
widget._base_stylesheet = base_stylesheet
|
||||
|
||||
if base_stylesheet is not None:
|
||||
scaled = _scale_stylesheet_font_sizes(base_stylesheet, _get_font_scale())
|
||||
widget._applied_stylesheet = scaled
|
||||
if current_stylesheet != scaled:
|
||||
widget.setStyleSheet(scaled)
|
||||
|
||||
|
||||
def _apply_scaled_font(widget):
|
||||
base_font = getattr(widget, '_base_font_for_scale', None)
|
||||
if base_font is None:
|
||||
app = QApplication.instance()
|
||||
app_base_font = app.property("base_app_font") if app else None
|
||||
base_font = QFont(app_base_font) if app_base_font is not None else QFont(widget.font())
|
||||
widget._base_font_for_scale = QFont(base_font)
|
||||
|
||||
scaled_font = QFont(base_font)
|
||||
scale = _get_font_scale()
|
||||
if base_font.pointSizeF() > 0:
|
||||
scaled_font.setPointSizeF(max(1.0, base_font.pointSizeF() * scale))
|
||||
elif base_font.pointSize() > 0:
|
||||
scaled_font.setPointSize(max(1, int(round(base_font.pointSize() * scale))))
|
||||
widget.setFont(scaled_font)
|
||||
|
||||
class DronePanel(QWidget):
|
||||
"""單個無人機面板類別"""
|
||||
|
||||
@@ -32,12 +99,13 @@ class DronePanel(QWidget):
|
||||
|
||||
self.attitude_indicator = None
|
||||
self._init_ui()
|
||||
self.apply_font_scale()
|
||||
|
||||
def _init_ui(self):
|
||||
"""初始化UI"""
|
||||
self.setObjectName(f"panel_{self.drone_id}")
|
||||
self.setFixedHeight(140)
|
||||
self.setStyleSheet("""
|
||||
_set_scaled_stylesheet(self, """
|
||||
background-color: #2A2A2A;
|
||||
border-radius: 8px;
|
||||
""")
|
||||
@@ -49,7 +117,7 @@ class DronePanel(QWidget):
|
||||
|
||||
# 創建內容容器(包含 info 和 control)
|
||||
content_widget = QWidget()
|
||||
content_widget.setStyleSheet("background-color: #333; border-radius: 6px;")
|
||||
_set_scaled_stylesheet(content_widget, "background-color: #333; border-radius: 6px;")
|
||||
content_layout = QHBoxLayout(content_widget)
|
||||
content_layout.setContentsMargins(8, 8, 8, 8)
|
||||
content_layout.setSpacing(8)
|
||||
@@ -82,7 +150,7 @@ class DronePanel(QWidget):
|
||||
# 勾選框
|
||||
self.checkbox = QCheckBox()
|
||||
self.checkbox.setObjectName(f"{self.drone_id}_checkbox")
|
||||
self.checkbox.setStyleSheet("""
|
||||
_set_scaled_stylesheet(self.checkbox, """
|
||||
QCheckBox {
|
||||
color: #DDD;
|
||||
}
|
||||
@@ -103,8 +171,8 @@ class DronePanel(QWidget):
|
||||
)
|
||||
|
||||
# ID 顯示
|
||||
id_label = QLabel(self.display_id)
|
||||
id_label.setStyleSheet("""
|
||||
self.id_label = QLabel(self.display_id)
|
||||
_set_scaled_stylesheet(self.id_label, """
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
color: #7FFFD4;
|
||||
@@ -112,7 +180,7 @@ class DronePanel(QWidget):
|
||||
""")
|
||||
|
||||
header_layout.addWidget(self.checkbox)
|
||||
header_layout.addWidget(id_label)
|
||||
header_layout.addWidget(self.id_label)
|
||||
header_layout.addStretch()
|
||||
|
||||
info_layout.addWidget(header)
|
||||
@@ -148,15 +216,15 @@ class DronePanel(QWidget):
|
||||
status_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
status_title = QLabel("狀態:")
|
||||
status_title.setStyleSheet("color: #888; min-width: 50px;")
|
||||
_set_scaled_stylesheet(status_title, "color: #888; min-width: 50px;")
|
||||
|
||||
self.mode_label = QLabel("--")
|
||||
self.mode_label.setObjectName(f"{self.drone_id}_mode")
|
||||
self.mode_label.setStyleSheet("color: #DDD;")
|
||||
_set_scaled_stylesheet(self.mode_label, "color: #DDD;")
|
||||
|
||||
self.armed_label = QLabel("--")
|
||||
self.armed_label.setObjectName(f"{self.drone_id}_armed")
|
||||
self.armed_label.setStyleSheet("color: #DDD;")
|
||||
_set_scaled_stylesheet(self.armed_label, "color: #DDD;")
|
||||
|
||||
status_layout.addWidget(status_title)
|
||||
status_layout.addWidget(self.mode_label)
|
||||
@@ -172,15 +240,15 @@ class DronePanel(QWidget):
|
||||
connection_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
connection_title = QLabel("Socket:")
|
||||
connection_title.setStyleSheet("color: #888; min-width: 50px;")
|
||||
_set_scaled_stylesheet(connection_title, "color: #888; min-width: 50px;")
|
||||
|
||||
# 根據解析的 drone_id 資訊設定初始值
|
||||
self.socket_seq_label = QLabel(self.socket_seq)
|
||||
self.socket_seq_label.setObjectName(f"{self.drone_id}_socket_seq")
|
||||
self.socket_seq_label.setStyleSheet("color: #DDD;")
|
||||
_set_scaled_stylesheet(self.socket_seq_label, "color: #DDD;")
|
||||
|
||||
connection_sep = QLabel(" - ")
|
||||
connection_sep.setStyleSheet("color: #DDD;")
|
||||
_set_scaled_stylesheet(connection_sep, "color: #DDD;")
|
||||
|
||||
# 設定連接方式顯示
|
||||
connection_type_map = {
|
||||
@@ -193,7 +261,7 @@ class DronePanel(QWidget):
|
||||
|
||||
self.connection_type_label = QLabel(connection_type)
|
||||
self.connection_type_label.setObjectName(f"{self.drone_id}_connection_type")
|
||||
self.connection_type_label.setStyleSheet("color: #DDD;")
|
||||
_set_scaled_stylesheet(self.connection_type_label, "color: #DDD;")
|
||||
|
||||
connection_layout.addWidget(connection_title)
|
||||
connection_layout.addWidget(self.socket_seq_label)
|
||||
@@ -210,29 +278,29 @@ class DronePanel(QWidget):
|
||||
battery_layout.setContentsMargins(0, 0, 0, 0)
|
||||
# 顯示百分比
|
||||
battery_title = QLabel("電池:")
|
||||
battery_title.setStyleSheet("color: #888; min-width: 50px;")
|
||||
_set_scaled_stylesheet(battery_title, "color: #888; min-width: 50px;")
|
||||
|
||||
self.battery_pct_label = QLabel("--")
|
||||
self.battery_pct_label.setObjectName(f"{self.drone_id}_battery_pct")
|
||||
self.battery_pct_label.setStyleSheet("color: #DDD;")
|
||||
_set_scaled_stylesheet(self.battery_pct_label, "color: #DDD;")
|
||||
|
||||
# 分隔符
|
||||
separator1 = QLabel(" - ")
|
||||
separator1.setStyleSheet("color: #DDD;")
|
||||
_set_scaled_stylesheet(separator1, "color: #DDD;")
|
||||
|
||||
# 顯示電壓
|
||||
self.battery_vol_label = QLabel("--")
|
||||
self.battery_vol_label.setObjectName(f"{self.drone_id}_battery_vol")
|
||||
self.battery_vol_label.setStyleSheet("color: #DDD;")
|
||||
_set_scaled_stylesheet(self.battery_vol_label, "color: #DDD;")
|
||||
|
||||
# 分隔符
|
||||
separator2 = QLabel(" - ")
|
||||
separator2.setStyleSheet("color: #DDD;")
|
||||
_set_scaled_stylesheet(separator2, "color: #DDD;")
|
||||
|
||||
# 顯示電池節數 (S count)
|
||||
self.battery_cells_label = QLabel("--")
|
||||
self.battery_cells_label.setObjectName(f"{self.drone_id}_battery_cells")
|
||||
self.battery_cells_label.setStyleSheet("color: #DDD;")
|
||||
_set_scaled_stylesheet(self.battery_cells_label, "color: #DDD;")
|
||||
|
||||
battery_layout.addWidget(battery_title)
|
||||
battery_layout.addWidget(self.battery_pct_label)
|
||||
@@ -251,18 +319,18 @@ class DronePanel(QWidget):
|
||||
altitude_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
altitude_title = QLabel("高度:")
|
||||
altitude_title.setStyleSheet("color: #888; min-width: 50px;")
|
||||
_set_scaled_stylesheet(altitude_title, "color: #888; min-width: 50px;")
|
||||
|
||||
self.altitude_label = QLabel("--")
|
||||
self.altitude_label.setObjectName(f"{self.drone_id}_altitude")
|
||||
self.altitude_label.setStyleSheet("color: #DDD;")
|
||||
_set_scaled_stylesheet(self.altitude_label, "color: #DDD;")
|
||||
|
||||
speed_title = QLabel("速度:")
|
||||
speed_title.setStyleSheet("color: #888; margin-left: 10px;")
|
||||
_set_scaled_stylesheet(speed_title, "color: #888; margin-left: 10px;")
|
||||
|
||||
self.speed_label = QLabel("--")
|
||||
self.speed_label.setObjectName(f"{self.drone_id}_speed")
|
||||
self.speed_label.setStyleSheet("color: #DDD;")
|
||||
_set_scaled_stylesheet(self.speed_label, "color: #DDD;")
|
||||
|
||||
altitude_layout.addWidget(altitude_title)
|
||||
altitude_layout.addWidget(self.altitude_label)
|
||||
@@ -320,6 +388,16 @@ class DronePanel(QWidget):
|
||||
def is_checked(self):
|
||||
"""獲取勾選狀態"""
|
||||
return self.checkbox.isChecked()
|
||||
|
||||
def apply_font_scale(self):
|
||||
"""重新套用目前字體倍率到 panel 內所有元件。"""
|
||||
_apply_scaled_font(self)
|
||||
_reapply_scaled_stylesheet(self)
|
||||
for child in self.findChildren(QWidget):
|
||||
_apply_scaled_font(child)
|
||||
_reapply_scaled_stylesheet(child)
|
||||
if self.attitude_indicator:
|
||||
self.attitude_indicator.update()
|
||||
|
||||
class SocketGroupPanel(QWidget):
|
||||
# 定義信號
|
||||
@@ -331,11 +409,12 @@ class SocketGroupPanel(QWidget):
|
||||
self.color = color
|
||||
self.socket_type = socket_type
|
||||
self._init_ui()
|
||||
self.apply_font_scale()
|
||||
|
||||
def _init_ui(self):
|
||||
"""初始化UI"""
|
||||
self.setObjectName(f"socket_group_{self.socket_id}")
|
||||
self.setStyleSheet("""
|
||||
_set_scaled_stylesheet(self, """
|
||||
background-color: #1E1E1E;
|
||||
border-radius: 12px;
|
||||
""")
|
||||
@@ -352,7 +431,7 @@ class SocketGroupPanel(QWidget):
|
||||
# 分組勾選框
|
||||
self.group_checkbox = QCheckBox()
|
||||
self.group_checkbox.setObjectName(f"socket_{self.socket_id}_checkbox")
|
||||
self.group_checkbox.setStyleSheet(f"""
|
||||
_set_scaled_stylesheet(self.group_checkbox, f"""
|
||||
QCheckBox {{ color: #DDD; }}
|
||||
QCheckBox::indicator {{
|
||||
width: 14px;
|
||||
@@ -380,7 +459,7 @@ class SocketGroupPanel(QWidget):
|
||||
else:
|
||||
title_text = f"Socket {self.socket_id}"
|
||||
self.title_label = QLabel(title_text)
|
||||
self.title_label.setStyleSheet(f"""
|
||||
_set_scaled_stylesheet(self.title_label, f"""
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
color: {self.color};
|
||||
@@ -430,6 +509,14 @@ class SocketGroupPanel(QWidget):
|
||||
"""設置分組勾選狀態(支持半選)"""
|
||||
self.group_checkbox.setCheckState(state)
|
||||
|
||||
def apply_font_scale(self):
|
||||
"""重新套用目前字體倍率到 socket 分組面板。"""
|
||||
_apply_scaled_font(self)
|
||||
_reapply_scaled_stylesheet(self)
|
||||
for child in self.findChildren(QWidget):
|
||||
_apply_scaled_font(child)
|
||||
_reapply_scaled_stylesheet(child)
|
||||
|
||||
|
||||
class AttitudeIndicator(QWidget):
|
||||
"""
|
||||
@@ -503,7 +590,9 @@ class AttitudeIndicator(QWidget):
|
||||
|
||||
# pitch ladder (every 10°, ±30°)
|
||||
p.setPen(QPen(QColor(255, 255, 255, 180), 1))
|
||||
p.setFont(QFont("Arial", 6))
|
||||
ladder_font = QFont("Arial")
|
||||
ladder_font.setPointSizeF(max(1.0, 6 * _get_font_scale()))
|
||||
p.setFont(ladder_font)
|
||||
for deg in range(-30, 31, 10):
|
||||
if deg == 0:
|
||||
continue
|
||||
@@ -581,6 +670,8 @@ class AttitudeIndicator(QWidget):
|
||||
|
||||
# heading text centred (bigger)
|
||||
p.setPen(QPen(QColor("#FFFFFF")))
|
||||
p.setFont(QFont("Arial", 10, QFont.Weight.Bold))
|
||||
heading_font = QFont("Arial", weight=QFont.Weight.Bold)
|
||||
heading_font.setPointSizeF(max(1.0, 10 * _get_font_scale()))
|
||||
p.setFont(heading_font)
|
||||
hdg_str = f"{int(self.heading)}°"
|
||||
p.drawText(0, int(strip_y), w, strip_h, Qt.AlignmentFlag.AlignCenter, hdg_str)
|
||||
p.drawText(0, int(strip_y), w, strip_h, Qt.AlignmentFlag.AlignCenter, hdg_str)
|
||||
|
||||
Reference in New Issue
Block a user