|
|
|
|
@ -0,0 +1,492 @@
|
|
|
|
|
# ui/components.py
|
|
|
|
|
from PyQt5.QtWidgets import QWidget, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QTextEdit, QProgressBar
|
|
|
|
|
from PyQt5.QtCore import Qt
|
|
|
|
|
|
|
|
|
|
class CustomTitleBar(QWidget):
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
"""
|
|
|
|
|
自定义标题栏
|
|
|
|
|
- 创建标题栏UI元素
|
|
|
|
|
- 添加窗口控制按钮
|
|
|
|
|
"""
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self.parent = parent
|
|
|
|
|
self.setup_ui()
|
|
|
|
|
|
|
|
|
|
def setup_ui(self):
|
|
|
|
|
"""
|
|
|
|
|
设置标题栏UI
|
|
|
|
|
- 初始化所有UI组件
|
|
|
|
|
- 设置组件属性和样式
|
|
|
|
|
"""
|
|
|
|
|
# 创建水平布局
|
|
|
|
|
layout = QHBoxLayout()
|
|
|
|
|
layout.setContentsMargins(10, 5, 10, 5)
|
|
|
|
|
layout.setSpacing(10)
|
|
|
|
|
|
|
|
|
|
# 创建标题标签
|
|
|
|
|
self.title_label = QLabel("MagicWord")
|
|
|
|
|
self.title_label.setStyleSheet("color: #333333; font-size: 12px; font-weight: normal;")
|
|
|
|
|
|
|
|
|
|
# 创建控制按钮
|
|
|
|
|
self.minimize_button = QPushButton("—")
|
|
|
|
|
self.maximize_button = QPushButton("□")
|
|
|
|
|
self.close_button = QPushButton("×")
|
|
|
|
|
|
|
|
|
|
# 设置按钮样式
|
|
|
|
|
button_style = """
|
|
|
|
|
QPushButton {
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
border: none;
|
|
|
|
|
color: #333333;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: normal;
|
|
|
|
|
width: 30px;
|
|
|
|
|
height: 30px;
|
|
|
|
|
}
|
|
|
|
|
QPushButton:hover {
|
|
|
|
|
background-color: #d0d0d0;
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
self.minimize_button.setStyleSheet(button_style)
|
|
|
|
|
self.maximize_button.setStyleSheet(button_style)
|
|
|
|
|
self.close_button.setStyleSheet(button_style + "QPushButton:hover { background-color: #ff5555; color: white; }")
|
|
|
|
|
|
|
|
|
|
# 添加组件到布局
|
|
|
|
|
layout.addWidget(self.title_label)
|
|
|
|
|
layout.addStretch()
|
|
|
|
|
layout.addWidget(self.minimize_button)
|
|
|
|
|
layout.addWidget(self.maximize_button)
|
|
|
|
|
layout.addWidget(self.close_button)
|
|
|
|
|
|
|
|
|
|
self.setLayout(layout)
|
|
|
|
|
|
|
|
|
|
# 连接按钮事件
|
|
|
|
|
self.minimize_button.clicked.connect(self.minimize_window)
|
|
|
|
|
self.maximize_button.clicked.connect(self.maximize_window)
|
|
|
|
|
self.close_button.clicked.connect(self.close_window)
|
|
|
|
|
|
|
|
|
|
# 设置标题栏样式
|
|
|
|
|
self.setStyleSheet("""
|
|
|
|
|
CustomTitleBar {
|
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
|
border-top-left-radius: 0px;
|
|
|
|
|
border-top-right-radius: 0px;
|
|
|
|
|
border-bottom: 1px solid #d0d0d0;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
def minimize_window(self):
|
|
|
|
|
"""
|
|
|
|
|
最小化窗口
|
|
|
|
|
- 触发窗口最小化事件
|
|
|
|
|
"""
|
|
|
|
|
if self.parent:
|
|
|
|
|
self.parent.showMinimized()
|
|
|
|
|
|
|
|
|
|
def maximize_window(self):
|
|
|
|
|
"""
|
|
|
|
|
最大化窗口
|
|
|
|
|
- 切换窗口最大化状态
|
|
|
|
|
"""
|
|
|
|
|
if self.parent:
|
|
|
|
|
if self.parent.isMaximized():
|
|
|
|
|
self.parent.showNormal()
|
|
|
|
|
self.maximize_button.setText("□")
|
|
|
|
|
else:
|
|
|
|
|
self.parent.showMaximized()
|
|
|
|
|
self.maximize_button.setText("❐")
|
|
|
|
|
|
|
|
|
|
def close_window(self):
|
|
|
|
|
"""
|
|
|
|
|
关闭窗口
|
|
|
|
|
- 触发窗口关闭事件
|
|
|
|
|
"""
|
|
|
|
|
if self.parent:
|
|
|
|
|
self.parent.close()
|
|
|
|
|
|
|
|
|
|
class ProgressBarWidget(QWidget):
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
"""
|
|
|
|
|
进度条组件
|
|
|
|
|
- 显示打字练习进度
|
|
|
|
|
- 显示统计信息
|
|
|
|
|
"""
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self.setup_ui()
|
|
|
|
|
|
|
|
|
|
def setup_ui(self):
|
|
|
|
|
"""
|
|
|
|
|
设置进度条UI
|
|
|
|
|
- 初始化所有UI组件
|
|
|
|
|
- 设置组件属性和样式
|
|
|
|
|
"""
|
|
|
|
|
# 创建垂直布局
|
|
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
layout.setContentsMargins(10, 10, 10, 10)
|
|
|
|
|
layout.setSpacing(5)
|
|
|
|
|
|
|
|
|
|
# 创建水平布局用于统计信息
|
|
|
|
|
stats_layout = QHBoxLayout()
|
|
|
|
|
stats_layout.setSpacing(15)
|
|
|
|
|
|
|
|
|
|
# 创建统计信息标签
|
|
|
|
|
self.wpm_label = QLabel("WPM: 0")
|
|
|
|
|
self.accuracy_label = QLabel("准确率: 0%")
|
|
|
|
|
self.time_label = QLabel("用时: 0s")
|
|
|
|
|
|
|
|
|
|
# 设置标签样式
|
|
|
|
|
label_style = "font-size: 12px; font-weight: normal; color: #333333;"
|
|
|
|
|
self.wpm_label.setStyleSheet(label_style)
|
|
|
|
|
self.accuracy_label.setStyleSheet(label_style)
|
|
|
|
|
self.time_label.setStyleSheet(label_style)
|
|
|
|
|
|
|
|
|
|
# 添加标签到统计布局
|
|
|
|
|
stats_layout.addWidget(self.wpm_label)
|
|
|
|
|
stats_layout.addWidget(self.accuracy_label)
|
|
|
|
|
stats_layout.addWidget(self.time_label)
|
|
|
|
|
stats_layout.addStretch()
|
|
|
|
|
|
|
|
|
|
# 创建进度条
|
|
|
|
|
self.progress_bar = QProgressBar()
|
|
|
|
|
self.progress_bar.setRange(0, 100)
|
|
|
|
|
self.progress_bar.setValue(0)
|
|
|
|
|
self.progress_bar.setTextVisible(True)
|
|
|
|
|
self.progress_bar.setFormat("进度: %p%")
|
|
|
|
|
|
|
|
|
|
# 设置进度条样式
|
|
|
|
|
self.progress_bar.setStyleSheet("""
|
|
|
|
|
QProgressBar {
|
|
|
|
|
border: 1px solid #c0c0c0;
|
|
|
|
|
border-radius: 0px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
|
}
|
|
|
|
|
QProgressBar::chunk {
|
|
|
|
|
background-color: #0078d7;
|
|
|
|
|
border-radius: 0px;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
# 添加组件到主布局
|
|
|
|
|
layout.addLayout(stats_layout)
|
|
|
|
|
layout.addWidget(self.progress_bar)
|
|
|
|
|
|
|
|
|
|
self.setLayout(layout)
|
|
|
|
|
|
|
|
|
|
def update_progress(self, progress: float):
|
|
|
|
|
"""
|
|
|
|
|
更新进度条
|
|
|
|
|
- 设置进度值
|
|
|
|
|
- 更新显示
|
|
|
|
|
"""
|
|
|
|
|
self.progress_bar.setValue(int(progress))
|
|
|
|
|
|
|
|
|
|
def update_stats(self, wpm: int, accuracy: float, time_elapsed: int):
|
|
|
|
|
"""
|
|
|
|
|
更新统计信息
|
|
|
|
|
- wpm: 每分钟字数
|
|
|
|
|
- accuracy: 准确率(%)
|
|
|
|
|
- time_elapsed: 用时(秒)
|
|
|
|
|
"""
|
|
|
|
|
self.wpm_label.setText(f"WPM: {wpm}")
|
|
|
|
|
self.accuracy_label.setText(f"准确率: {accuracy:.1f}%")
|
|
|
|
|
self.time_label.setText(f"用时: {time_elapsed}s")
|
|
|
|
|
|
|
|
|
|
class StatsDisplayWidget(QWidget):
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
"""
|
|
|
|
|
统计信息显示组件
|
|
|
|
|
- 显示准确率、WPM等统计信息
|
|
|
|
|
"""
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self.setup_ui()
|
|
|
|
|
|
|
|
|
|
def setup_ui(self):
|
|
|
|
|
"""
|
|
|
|
|
设置统计信息显示UI
|
|
|
|
|
- 初始化所有UI组件
|
|
|
|
|
- 设置组件属性和样式
|
|
|
|
|
"""
|
|
|
|
|
# 创建水平布局
|
|
|
|
|
layout = QHBoxLayout()
|
|
|
|
|
layout.setContentsMargins(10, 5, 10, 5)
|
|
|
|
|
layout.setSpacing(15)
|
|
|
|
|
|
|
|
|
|
# 创建统计信息标签
|
|
|
|
|
self.wpm_label = QLabel("WPM: 0")
|
|
|
|
|
self.accuracy_label = QLabel("准确率: 0%")
|
|
|
|
|
|
|
|
|
|
# 设置标签样式
|
|
|
|
|
label_style = "font-size: 12px; font-weight: normal; color: #333333;"
|
|
|
|
|
self.wpm_label.setStyleSheet(label_style)
|
|
|
|
|
self.accuracy_label.setStyleSheet(label_style)
|
|
|
|
|
|
|
|
|
|
# 添加组件到布局
|
|
|
|
|
layout.addWidget(self.wpm_label)
|
|
|
|
|
layout.addWidget(self.accuracy_label)
|
|
|
|
|
layout.addStretch()
|
|
|
|
|
|
|
|
|
|
self.setLayout(layout)
|
|
|
|
|
|
|
|
|
|
# 设置样式
|
|
|
|
|
self.setStyleSheet("""
|
|
|
|
|
StatsDisplayWidget {
|
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
|
border-bottom: 1px solid #d0d0d0;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
def update_stats(self, wpm: int, accuracy: float):
|
|
|
|
|
"""
|
|
|
|
|
更新统计信息
|
|
|
|
|
- wpm: 每分钟字数
|
|
|
|
|
- accuracy: 准确率(%)
|
|
|
|
|
"""
|
|
|
|
|
self.wpm_label.setText(f"WPM: {wpm}")
|
|
|
|
|
self.accuracy_label.setText(f"准确率: {accuracy:.1f}%")
|
|
|
|
|
|
|
|
|
|
class QuoteDisplayWidget(QWidget):
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
"""
|
|
|
|
|
每日一言显示组件
|
|
|
|
|
- 显示每日一言功能
|
|
|
|
|
"""
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self.setup_ui()
|
|
|
|
|
|
|
|
|
|
def setup_ui(self):
|
|
|
|
|
"""
|
|
|
|
|
设置每日一言显示UI
|
|
|
|
|
- 初始化所有UI组件
|
|
|
|
|
- 设置组件属性和样式
|
|
|
|
|
"""
|
|
|
|
|
# 创建水平布局
|
|
|
|
|
layout = QHBoxLayout()
|
|
|
|
|
layout.setContentsMargins(10, 5, 10, 5)
|
|
|
|
|
layout.setSpacing(15)
|
|
|
|
|
|
|
|
|
|
# 创建每日一言标签
|
|
|
|
|
self.quote_label = QLabel("每日一言: 暂无")
|
|
|
|
|
self.quote_label.setStyleSheet("QLabel { color: #666666; font-style: italic; }")
|
|
|
|
|
|
|
|
|
|
# 设置标签样式
|
|
|
|
|
label_style = "font-size: 12px; font-weight: normal; color: #333333;"
|
|
|
|
|
self.quote_label.setStyleSheet(label_style)
|
|
|
|
|
|
|
|
|
|
# 创建每日一言刷新按钮
|
|
|
|
|
self.refresh_quote_button = QPushButton("刷新")
|
|
|
|
|
self.refresh_quote_button.setStyleSheet("""
|
|
|
|
|
QPushButton {
|
|
|
|
|
background-color: #0078d7;
|
|
|
|
|
color: white;
|
|
|
|
|
border: none;
|
|
|
|
|
padding: 5px 10px;
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
QPushButton:hover {
|
|
|
|
|
background-color: #005a9e;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
# 添加组件到布局
|
|
|
|
|
layout.addWidget(self.quote_label)
|
|
|
|
|
layout.addStretch()
|
|
|
|
|
layout.addWidget(self.refresh_quote_button)
|
|
|
|
|
|
|
|
|
|
self.setLayout(layout)
|
|
|
|
|
|
|
|
|
|
# 设置样式
|
|
|
|
|
self.setStyleSheet("""
|
|
|
|
|
QuoteDisplayWidget {
|
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
|
border-bottom: 1px solid #d0d0d0;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
def update_quote(self, quote: str):
|
|
|
|
|
"""
|
|
|
|
|
更新每日一言
|
|
|
|
|
- quote: 每日一言内容
|
|
|
|
|
"""
|
|
|
|
|
self.quote_label.setText(f"每日一言: {quote}")
|
|
|
|
|
|
|
|
|
|
class WeatherDisplayWidget(QWidget):
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
"""
|
|
|
|
|
天气显示组件
|
|
|
|
|
- 显示天气信息
|
|
|
|
|
"""
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self.setup_ui()
|
|
|
|
|
|
|
|
|
|
def setup_ui(self):
|
|
|
|
|
"""
|
|
|
|
|
设置天气显示UI
|
|
|
|
|
- 初始化所有UI组件
|
|
|
|
|
- 设置组件属性和样式
|
|
|
|
|
"""
|
|
|
|
|
# 创建水平布局
|
|
|
|
|
layout = QHBoxLayout()
|
|
|
|
|
layout.setContentsMargins(10, 5, 10, 5)
|
|
|
|
|
layout.setSpacing(15)
|
|
|
|
|
|
|
|
|
|
# 创建天气信息标签
|
|
|
|
|
self.weather_label = QLabel("天气: 暂无")
|
|
|
|
|
|
|
|
|
|
# 设置标签样式
|
|
|
|
|
label_style = "font-size: 12px; font-weight: normal; color: #333333;"
|
|
|
|
|
self.weather_label.setStyleSheet(label_style)
|
|
|
|
|
|
|
|
|
|
# 创建天气刷新按钮
|
|
|
|
|
self.refresh_weather_button = QPushButton("刷新")
|
|
|
|
|
self.refresh_weather_button.setStyleSheet("""
|
|
|
|
|
QPushButton {
|
|
|
|
|
background-color: #0078d7;
|
|
|
|
|
color: white;
|
|
|
|
|
border: none;
|
|
|
|
|
padding: 5px 10px;
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
QPushButton:hover {
|
|
|
|
|
background-color: #005a9e;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
# 添加组件到布局
|
|
|
|
|
layout.addWidget(self.weather_label)
|
|
|
|
|
layout.addStretch()
|
|
|
|
|
layout.addWidget(self.refresh_weather_button)
|
|
|
|
|
|
|
|
|
|
self.setLayout(layout)
|
|
|
|
|
|
|
|
|
|
# 设置样式
|
|
|
|
|
self.setStyleSheet("""
|
|
|
|
|
WeatherDisplayWidget {
|
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
|
border-bottom: 1px solid #d0d0d0;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
def update_weather(self, weather_info: dict):
|
|
|
|
|
"""
|
|
|
|
|
更新天气信息
|
|
|
|
|
- weather_info: 天气信息字典
|
|
|
|
|
"""
|
|
|
|
|
if weather_info:
|
|
|
|
|
city = weather_info.get("city", "未知")
|
|
|
|
|
temperature = weather_info.get("temperature", "N/A")
|
|
|
|
|
description = weather_info.get("description", "N/A")
|
|
|
|
|
self.weather_label.setText(f"天气: {city} {temperature}°C {description}")
|
|
|
|
|
else:
|
|
|
|
|
self.weather_label.setText("天气: 获取失败")
|
|
|
|
|
|
|
|
|
|
class TextDisplayWidget(QWidget):
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
"""
|
|
|
|
|
文本显示组件
|
|
|
|
|
- 显示待练习文本
|
|
|
|
|
- 高亮当前字符
|
|
|
|
|
- 显示用户输入反馈
|
|
|
|
|
"""
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self.text_content = ""
|
|
|
|
|
self.current_index = 0
|
|
|
|
|
self.setup_ui()
|
|
|
|
|
|
|
|
|
|
def setup_ui(self):
|
|
|
|
|
"""
|
|
|
|
|
设置文本显示UI
|
|
|
|
|
- 初始化文本显示区域
|
|
|
|
|
- 设置样式和布局
|
|
|
|
|
"""
|
|
|
|
|
# 创建垂直布局
|
|
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
|
|
|
|
|
|
# 创建文本显示区域
|
|
|
|
|
self.text_display = QTextEdit()
|
|
|
|
|
self.text_display.setReadOnly(False) # 设置为可编辑
|
|
|
|
|
self.text_display.setLineWrapMode(QTextEdit.WidgetWidth)
|
|
|
|
|
|
|
|
|
|
# 设置文本显示样式
|
|
|
|
|
self.text_display.setStyleSheet("""
|
|
|
|
|
QTextEdit {
|
|
|
|
|
font-family: 'Calibri', 'Segoe UI', 'Microsoft YaHei', sans-serif;
|
|
|
|
|
font-size: 12pt;
|
|
|
|
|
border: 1px solid #d0d0d0;
|
|
|
|
|
border-radius: 0px;
|
|
|
|
|
padding: 15px;
|
|
|
|
|
background-color: white;
|
|
|
|
|
color: black;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
# 添加组件到布局
|
|
|
|
|
layout.addWidget(self.text_display)
|
|
|
|
|
self.setLayout(layout)
|
|
|
|
|
|
|
|
|
|
def set_text(self, text: str):
|
|
|
|
|
"""
|
|
|
|
|
设置显示文本
|
|
|
|
|
- text: 要显示的文本内容
|
|
|
|
|
"""
|
|
|
|
|
self.text_content = text
|
|
|
|
|
self.current_index = 0
|
|
|
|
|
# 初始不显示内容,通过打字逐步显示
|
|
|
|
|
self.text_display.setHtml("")
|
|
|
|
|
|
|
|
|
|
def highlight_character(self, position: int):
|
|
|
|
|
"""
|
|
|
|
|
高亮指定位置的字符
|
|
|
|
|
- position: 字符位置索引
|
|
|
|
|
"""
|
|
|
|
|
if 0 <= position < len(self.text_content):
|
|
|
|
|
self.current_index = position
|
|
|
|
|
# 不再直接高亮字符,而是通过用户输入来显示内容
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def _update_display(self, user_input: str = ""):
|
|
|
|
|
"""
|
|
|
|
|
更新文本显示
|
|
|
|
|
- user_input: 用户输入文本(可选)
|
|
|
|
|
"""
|
|
|
|
|
# 导入需要的模块
|
|
|
|
|
from PyQt5.QtGui import QTextCursor
|
|
|
|
|
|
|
|
|
|
if not self.text_content:
|
|
|
|
|
self.text_display.clear()
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 简单显示文本,不使用任何高亮
|
|
|
|
|
if user_input:
|
|
|
|
|
# 只显示用户已输入的部分文本
|
|
|
|
|
displayed_text = self.text_content[:len(user_input)]
|
|
|
|
|
else:
|
|
|
|
|
# 没有用户输入,不显示任何内容
|
|
|
|
|
displayed_text = ""
|
|
|
|
|
|
|
|
|
|
# 更新文本显示
|
|
|
|
|
self.text_display.setPlainText(displayed_text)
|
|
|
|
|
|
|
|
|
|
# 安全地滚动到光标位置
|
|
|
|
|
if user_input and displayed_text:
|
|
|
|
|
try:
|
|
|
|
|
cursor = self.text_display.textCursor()
|
|
|
|
|
# 将光标定位到文本末尾
|
|
|
|
|
cursor.setPosition(len(displayed_text))
|
|
|
|
|
self.text_display.setTextCursor(cursor)
|
|
|
|
|
self.text_display.ensureCursorVisible()
|
|
|
|
|
except Exception:
|
|
|
|
|
# 如果光标定位失败,忽略错误
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def show_user_input(self, input_text: str):
|
|
|
|
|
"""
|
|
|
|
|
显示用户输入的文本
|
|
|
|
|
- input_text: 用户输入的文本
|
|
|
|
|
"""
|
|
|
|
|
self._update_display(input_text)
|