簡化 gui 設置版面

This commit is contained in:
Minidoracat 2025-06-03 19:55:56 +08:00
parent 00bbf1e113
commit a72a858444
6 changed files with 263 additions and 105 deletions

View File

@ -12,6 +12,7 @@ from PySide6.QtWidgets import (
QComboBox, QRadioButton, QButtonGroup, QMessageBox,
QCheckBox, QPushButton, QFrame
)
from ..widgets import SwitchWithLabel
from PySide6.QtCore import Signal, Qt
from PySide6.QtGui import QFont
@ -152,11 +153,6 @@ class SettingsTab(QWidget):
# 保存引用以便更新
self.ui_elements['language_header'] = header
desc = self._create_description(t('settings.language.description'))
layout.addWidget(desc)
# 保存引用以便更新
self.ui_elements['language_desc'] = desc
# 語言選擇器容器
lang_container = QHBoxLayout()
lang_container.setContentsMargins(0, 0, 0, 0)
@ -210,11 +206,6 @@ class SettingsTab(QWidget):
# 保存引用以便更新
self.ui_elements['layout_header'] = header
desc = self._create_description(t('settings.layout.description'))
layout.addWidget(desc)
# 保存引用以便更新
self.ui_elements['layout_desc'] = desc
# 選項容器
options_layout = QVBoxLayout()
options_layout.setSpacing(2)
@ -304,60 +295,15 @@ class SettingsTab(QWidget):
# 保存引用以便更新
self.ui_elements['window_header'] = header
desc = self._create_description(t('settings.window.alwaysCenterDescription'))
layout.addWidget(desc)
# 保存引用以便更新
self.ui_elements['window_desc'] = desc
# 選項容器
options_layout = QVBoxLayout()
options_layout.setSpacing(2)
options_layout.setSpacing(8)
self.always_center_checkbox = QCheckBox(t('settings.window.alwaysCenter'))
self.always_center_checkbox.setChecked(self.config_manager.get_always_center_window())
self.always_center_checkbox.setStyleSheet("""
QCheckBox {
font-family: "Microsoft JhengHei", "微軟正黑體", sans-serif;
font-size: 13px;
color: #ffffff;
spacing: 8px;
padding: 2px 0px;
}
QCheckBox::indicator {
width: 16px;
height: 16px;
}
QCheckBox::indicator:unchecked {
border: 2px solid #666666;
border-radius: 3px;
background-color: transparent;
}
QCheckBox::indicator:checked {
border: 2px solid #0078d4;
border-radius: 3px;
background-color: #0078d4;
image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTIiIHZpZXdCb3g9IjAgMCAxMiAxMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEwIDNMNC41IDguNUwyIDYiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+);
}
QCheckBox::indicator:hover {
border-color: #0078d4;
}
""")
self.always_center_checkbox.stateChanged.connect(self._on_always_center_changed)
options_layout.addWidget(self.always_center_checkbox)
center_hint = QLabel(f" {t('settings.window.alwaysCenterDescription')}")
center_hint.setStyleSheet("""
QLabel {
font-family: "Microsoft JhengHei", "微軟正黑體", sans-serif;
color: #888888;
font-size: 11px;
margin-left: 20px;
margin-bottom: 4px;
}
""")
options_layout.addWidget(center_hint)
# 保存引用以便更新
self.ui_elements['center_hint'] = center_hint
# 使用現代化的 Switch 組件
self.always_center_switch = SwitchWithLabel(t('settings.window.alwaysCenter'))
self.always_center_switch.setChecked(self.config_manager.get_always_center_window())
self.always_center_switch.toggled.connect(self._on_always_center_changed)
options_layout.addWidget(self.always_center_switch)
layout.addLayout(options_layout)
@ -368,11 +314,6 @@ class SettingsTab(QWidget):
# 保存引用以便更新
self.ui_elements['reset_header'] = header
desc = self._create_description(t('settings.reset.description'))
layout.addWidget(desc)
# 保存引用以便更新
self.ui_elements['reset_desc'] = desc
reset_container = QHBoxLayout()
reset_container.setContentsMargins(0, 0, 0, 0)
@ -475,12 +416,11 @@ class SettingsTab(QWidget):
# 發出佈局變更請求信號
self.layout_change_requested.emit(new_combined_mode, new_orientation)
def _on_always_center_changed(self, state: int) -> None:
def _on_always_center_changed(self, checked: bool) -> None:
"""視窗定位選項變更事件處理"""
always_center = state == Qt.CheckState.Checked.value
# 立即保存設定
self.config_manager.set_always_center_window(always_center)
debug_log(f"視窗定位設置已保存: {always_center}") # 調試輸出
self.config_manager.set_always_center_window(checked)
debug_log(f"視窗定位設置已保存: {checked}") # 調試輸出
def _on_reset_settings(self) -> None:
"""重置設定事件處理"""
@ -507,15 +447,7 @@ class SettingsTab(QWidget):
if 'reset_header' in self.ui_elements:
self.ui_elements['reset_header'].setText(f"🔄 {t('settings.reset.title')}")
# 更新描述文字
if 'language_desc' in self.ui_elements:
self.ui_elements['language_desc'].setText(t('settings.language.description'))
if 'layout_desc' in self.ui_elements:
self.ui_elements['layout_desc'].setText(t('settings.layout.description'))
if 'window_desc' in self.ui_elements:
self.ui_elements['window_desc'].setText(t('settings.window.alwaysCenterDescription'))
if 'reset_desc' in self.ui_elements:
self.ui_elements['reset_desc'].setText(t('settings.reset.description'))
# 更新提示文字
if 'separate_hint' in self.ui_elements:
@ -524,16 +456,14 @@ class SettingsTab(QWidget):
self.ui_elements['vertical_hint'].setText(f" {t('settings.layout.combinedVerticalDescription')}")
if 'horizontal_hint' in self.ui_elements:
self.ui_elements['horizontal_hint'].setText(f" {t('settings.layout.combinedHorizontalDescription')}")
if 'center_hint' in self.ui_elements:
self.ui_elements['center_hint'].setText(f" {t('settings.window.alwaysCenterDescription')}")
# 更新按鈕文字
if hasattr(self, 'reset_button'):
self.reset_button.setText(t('settings.reset.button'))
# 更新複選框文字
if hasattr(self, 'always_center_checkbox'):
self.always_center_checkbox.setText(t('settings.window.alwaysCenter'))
# 更新切換開關文字
if hasattr(self, 'always_center_switch'):
self.always_center_switch.setText(t('settings.window.alwaysCenter'))
# 更新單選按鈕文字
if hasattr(self, 'separate_mode_radio'):
@ -557,9 +487,9 @@ class SettingsTab(QWidget):
self._set_initial_layout_state()
# 重新載入視窗設定
if hasattr(self, 'always_center_checkbox'):
if hasattr(self, 'always_center_switch'):
always_center = self.config_manager.get_always_center_window()
self.always_center_checkbox.setChecked(always_center)
self.always_center_switch.setChecked(always_center)
debug_log(f"重新載入視窗定位設置: {always_center}") # 調試輸出
def set_layout_mode(self, combined_mode: bool) -> None:

View File

@ -8,9 +8,12 @@ GUI 自定義元件模組
from .text_edit import SmartTextEdit
from .image_preview import ImagePreviewWidget
from .image_upload import ImageUploadWidget
from .switch import SwitchWidget, SwitchWithLabel
__all__ = [
'SmartTextEdit',
'ImagePreviewWidget',
'ImageUploadWidget'
'ImageUploadWidget',
'SwitchWidget',
'SwitchWithLabel'
]

View File

@ -0,0 +1,237 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
現代化切換開關組件
==================
提供類似 web 的現代化 switch 切換開關
"""
from PySide6.QtWidgets import QWidget, QHBoxLayout, QLabel, QToolTip
from PySide6.QtCore import Signal, QPropertyAnimation, QRect, QEasingCurve, Property, Qt, QTimer
from PySide6.QtGui import QPainter, QColor, QPainterPath, QFont
class SwitchWidget(QWidget):
"""現代化切換開關組件"""
toggled = Signal(bool) # 狀態變更信號
def __init__(self, parent=None):
super().__init__(parent)
# 狀態變數
self._checked = False
self._enabled = True
self._animating = False
# 尺寸設定
self._width = 50
self._height = 24
self._thumb_radius = 10
self._track_radius = 12
# 顏色設定
self._track_color_off = QColor(102, 102, 102) # #666666
self._track_color_on = QColor(0, 120, 212) # #0078d4
self._thumb_color = QColor(255, 255, 255) # white
self._track_color_disabled = QColor(68, 68, 68) # #444444
# 動畫屬性
self._thumb_position = 2.0
# 設定基本屬性
self.setFixedSize(self._width, self._height)
self.setCursor(Qt.PointingHandCursor)
# 創建動畫
self._animation = QPropertyAnimation(self, b"thumbPosition")
self._animation.setDuration(200) # 200ms 動畫時間
self._animation.setEasingCurve(QEasingCurve.OutCubic)
# 設置工具提示延遲
self._tooltip_timer = QTimer()
self._tooltip_timer.setSingleShot(True)
self._tooltip_timer.timeout.connect(self._show_delayed_tooltip)
@Property(float)
def thumbPosition(self):
return self._thumb_position
@thumbPosition.setter
def thumbPosition(self, position):
self._thumb_position = position
self.update()
def isChecked(self) -> bool:
"""獲取選中狀態"""
return self._checked
def setChecked(self, checked: bool) -> None:
"""設置選中狀態"""
if self._checked != checked:
self._checked = checked
self._animate_to_position()
self.toggled.emit(checked)
def setEnabled(self, enabled: bool) -> None:
"""設置啟用狀態"""
super().setEnabled(enabled)
self._enabled = enabled
self.setCursor(Qt.PointingHandCursor if enabled else Qt.ArrowCursor)
self.update()
def _animate_to_position(self) -> None:
"""動畫到目標位置"""
if self._animating:
return
self._animating = True
target_position = self._width - self._thumb_radius * 2 - 2 if self._checked else 2
self._animation.setStartValue(self._thumb_position)
self._animation.setEndValue(target_position)
self._animation.finished.connect(self._on_animation_finished)
self._animation.start()
def _on_animation_finished(self) -> None:
"""動畫完成處理"""
self._animating = False
self._animation.finished.disconnect()
def mousePressEvent(self, event):
"""滑鼠按下事件"""
if event.button() == Qt.LeftButton and self._enabled:
self.setChecked(not self._checked)
super().mousePressEvent(event)
def enterEvent(self, event):
"""滑鼠進入事件"""
if self._enabled:
# 延遲顯示工具提示
self._tooltip_timer.start(500) # 500ms 延遲
super().enterEvent(event)
def leaveEvent(self, event):
"""滑鼠離開事件"""
self._tooltip_timer.stop()
super().leaveEvent(event)
def _show_delayed_tooltip(self):
"""顯示延遲的工具提示"""
if self.toolTip():
QToolTip.showText(self.mapToGlobal(self.rect().center()), self.toolTip(), self)
def paintEvent(self, event):
"""繪製事件"""
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
# 計算軌道矩形
track_rect = QRect(0, (self._height - self._track_radius * 2) // 2,
self._width, self._track_radius * 2)
# 繪製軌道
track_path = QPainterPath()
track_path.addRoundedRect(track_rect, self._track_radius, self._track_radius)
if not self._enabled:
track_color = self._track_color_disabled
elif self._checked:
track_color = self._track_color_on
else:
track_color = self._track_color_off
painter.fillPath(track_path, track_color)
# 繪製滑塊
thumb_x = self._thumb_position
thumb_y = (self._height - self._thumb_radius * 2) // 2
thumb_rect = QRect(int(thumb_x), thumb_y, self._thumb_radius * 2, self._thumb_radius * 2)
thumb_path = QPainterPath()
thumb_path.addEllipse(thumb_rect)
# 滑塊顏色(可以根據狀態調整透明度)
thumb_color = self._thumb_color
if not self._enabled:
thumb_color.setAlpha(180) # 半透明效果
painter.fillPath(thumb_path, thumb_color)
# 添加微妙的陰影效果
if self._enabled:
shadow_color = QColor(0, 0, 0, 30)
shadow_rect = thumb_rect.translated(0, 1)
shadow_path = QPainterPath()
shadow_path.addEllipse(shadow_rect)
painter.fillPath(shadow_path, shadow_color)
class SwitchWithLabel(QWidget):
"""帶標籤的切換開關組件"""
toggled = Signal(bool)
def __init__(self, text: str = "", parent=None):
super().__init__(parent)
# 創建布局
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(12)
# 創建標籤
self.label = QLabel(text)
self.label.setStyleSheet("""
QLabel {
font-family: "Microsoft JhengHei", "微軟正黑體", sans-serif;
font-size: 13px;
color: #ffffff;
}
""")
# 創建切換開關
self.switch = SwitchWidget()
self.switch.toggled.connect(self.toggled.emit)
# 添加到布局
layout.addWidget(self.label)
layout.addStretch() # 彈性空間,將開關推到右側
layout.addWidget(self.switch)
# 設置點擊標籤也能切換開關
self.label.mousePressEvent = self._on_label_clicked
def _on_label_clicked(self, event):
"""標籤點擊事件"""
if event.button() == Qt.LeftButton:
self.switch.setChecked(not self.switch.isChecked())
def setText(self, text: str) -> None:
"""設置標籤文字"""
self.label.setText(text)
def text(self) -> str:
"""獲取標籤文字"""
return self.label.text()
def isChecked(self) -> bool:
"""獲取選中狀態"""
return self.switch.isChecked()
def setChecked(self, checked: bool) -> None:
"""設置選中狀態"""
self.switch.setChecked(checked)
def setEnabled(self, enabled: bool) -> None:
"""設置啟用狀態"""
super().setEnabled(enabled)
self.switch.setEnabled(enabled)
self.label.setStyleSheet(f"""
QLabel {{
font-family: "Microsoft JhengHei", "微軟正黑體", sans-serif;
font-size: 13px;
color: {"#ffffff" if enabled else "#888888"};
}}
""")

View File

@ -87,12 +87,10 @@
"title": "Application Settings",
"language": {
"title": "Language Settings",
"selector": "🌐 Language Selection",
"description": "Choose your preferred interface language. Language changes take effect immediately."
"selector": "🌐 Language Selection"
},
"layout": {
"title": "Interface Layout",
"description": "Choose the display method for AI summary and feedback input areas",
"separateMode": "Separate Mode",
"separateModeDescription": "AI summary and feedback are in separate tabs",
"combinedVertical": "Combined Mode (Vertical Layout)",
@ -102,12 +100,10 @@
},
"window": {
"title": "Window Positioning",
"alwaysCenter": "Always show window at primary screen center",
"alwaysCenterDescription": "Recommended for multi-monitor setups or when experiencing window positioning issues"
"alwaysCenter": "Always show window at primary screen center"
},
"reset": {
"title": "Reset Settings",
"description": "Clear all saved settings and restore to default state",
"button": "Reset Settings",
"confirmTitle": "Confirm Reset Settings",
"confirmMessage": "Are you sure you want to reset all settings? This will clear all saved preferences and restore to default state.",

View File

@ -67,12 +67,10 @@
"title": "应用设置",
"language": {
"title": "语言设置",
"selector": "🌐 语言选择",
"description": "选择您偏好的界面语言。语言更改会立即生效。"
"selector": "🌐 语言选择"
},
"layout": {
"title": "界面布局",
"description": "选择 AI 摘要和反馈输入区域的显示方式",
"separateMode": "分离模式",
"separateModeDescription": "AI 摘要和反馈分别在不同页签",
"combinedVertical": "合并模式(垂直布局)",
@ -82,12 +80,10 @@
},
"window": {
"title": "窗口定位",
"alwaysCenter": "总是在主屏幕中心显示窗口",
"alwaysCenterDescription": "建议在多屏幕环境或遇到窗口定位问题时开启此选项"
"alwaysCenter": "总是在主屏幕中心显示窗口"
},
"reset": {
"title": "重置设置",
"description": "清除所有已保存的设置,恢复到默认状态",
"button": "重置设置",
"confirmTitle": "确认重置设置",
"confirmMessage": "确定要重置所有设置吗?这将清除所有已保存的偏好设置并恢复到默认状态。",

View File

@ -83,12 +83,10 @@
"title": "應用設置",
"language": {
"title": "語言設置",
"selector": "🌐 語言選擇",
"description": "選擇您偏好的界面語言。語言變更會立即生效。"
"selector": "🌐 語言選擇"
},
"layout": {
"title": "界面佈局",
"description": "選擇 AI 摘要和回饋輸入區域的顯示方式",
"separateMode": "分離模式",
"separateModeDescription": "AI 摘要和回饋分別在不同頁籤",
"combinedVertical": "合併模式(垂直布局)",
@ -98,12 +96,10 @@
},
"window": {
"title": "視窗定位",
"alwaysCenter": "總是在主螢幕中心顯示視窗",
"alwaysCenterDescription": "建議在多螢幕環境或遇到視窗定位問題時開啟此選項"
"alwaysCenter": "總是在主螢幕中心顯示視窗"
},
"reset": {
"title": "重置設定",
"description": "清除所有已保存的設定,恢復到預設狀態",
"button": "重置設定",
"confirmTitle": "確認重置設定",
"confirmMessage": "確定要重置所有設定嗎?這將清除所有已保存的偏好設定並恢復到預設狀態。",