init
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, QLabel
|
||||
from PyQt6.QtCore import Qt, pyqtSignal, QPoint
|
||||
from PyQt6.QtGui import QColor, QTextCursor, QMouseEvent
|
||||
|
||||
from .config import OverlayCfg
|
||||
|
||||
_TITLE = "AI 答题助手"
|
||||
_LOADING_TEXT = "正在分析截图,请稍候..."
|
||||
|
||||
|
||||
class _CloseBtn(QLabel):
|
||||
def __init__(self, on_click, parent=None):
|
||||
super().__init__("×", parent)
|
||||
self._on_click = on_click
|
||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
|
||||
def mousePressEvent(self, event) -> None:
|
||||
if event.button() == Qt.MouseButton.LeftButton:
|
||||
self._on_click()
|
||||
|
||||
|
||||
class _TitleBar(QWidget):
|
||||
def __init__(self, win: "OverlayWindow", cfg: OverlayCfg):
|
||||
super().__init__(win)
|
||||
self._win = win
|
||||
self.setFixedHeight(24)
|
||||
self.setStyleSheet(f"background-color: {cfg.bg_color}; border-radius: 6px 6px 0 0;")
|
||||
|
||||
self.setMouseTracking(True)
|
||||
self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
|
||||
|
||||
self._drag_pos = None
|
||||
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 0, 6, 0)
|
||||
|
||||
title_lbl = QLabel(f"· {_TITLE}")
|
||||
title_lbl.setStyleSheet(f"color: {cfg.accent_color}; font-size: 11px;")
|
||||
# 让标签对鼠标透明,事件穿透到 _TitleBar
|
||||
title_lbl.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
|
||||
|
||||
close_btn = _CloseBtn(win._on_hide)
|
||||
close_btn.setStyleSheet(f"color: {cfg.accent_color}; padding: 0 4px; font-size: 14px;")
|
||||
|
||||
layout.addWidget(title_lbl)
|
||||
layout.addStretch()
|
||||
layout.addWidget(close_btn)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() == Qt.MouseButton.LeftButton:
|
||||
# 👇 就是在这里用 frameGeometry()
|
||||
self._drag_pos = event.globalPosition().toPoint() - self._win.frameGeometry().topLeft()
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if self._drag_pos is not None and event.buttons() & Qt.MouseButton.LeftButton:
|
||||
self._win.move(event.globalPosition().toPoint() - self._drag_pos)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
self._drag_pos = None
|
||||
|
||||
|
||||
|
||||
class OverlayWindow(QWidget):
|
||||
_sig_append = pyqtSignal(str)
|
||||
_sig_loading = pyqtSignal()
|
||||
_sig_clear = pyqtSignal()
|
||||
_sig_show = pyqtSignal()
|
||||
_sig_hide = pyqtSignal()
|
||||
_sig_toggle = pyqtSignal()
|
||||
|
||||
def __init__(self, cfg: OverlayCfg):
|
||||
self._app = QApplication.instance() or QApplication([])
|
||||
super().__init__()
|
||||
self._cfg = cfg
|
||||
self._drag_pos: QPoint | None = None
|
||||
|
||||
self._sig_append.connect(self._on_append)
|
||||
self._sig_loading.connect(self._on_loading)
|
||||
self._sig_clear.connect(self._on_clear)
|
||||
self._sig_show.connect(self._on_show)
|
||||
self._sig_hide.connect(self._on_hide)
|
||||
self._sig_toggle.connect(self._on_toggle)
|
||||
|
||||
self._setup_window()
|
||||
self._setup_ui()
|
||||
self._position_bottom_right()
|
||||
self.show()
|
||||
|
||||
def _setup_window(self) -> None:
|
||||
self.setWindowFlags(
|
||||
Qt.WindowType.WindowStaysOnTopHint |
|
||||
Qt.WindowType.Tool
|
||||
)
|
||||
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||
self.setWindowOpacity(self._cfg.alpha)
|
||||
self.resize(self._cfg.width, self._cfg.height)
|
||||
|
||||
def _setup_ui(self) -> None:
|
||||
cfg = self._cfg
|
||||
outer = QVBoxLayout(self)
|
||||
outer.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
container = QWidget()
|
||||
container.setObjectName("container")
|
||||
container.setStyleSheet(f"""
|
||||
#container {{
|
||||
background-color: {cfg.bg_color};
|
||||
border-radius: 6px;
|
||||
}}
|
||||
""")
|
||||
outer.addWidget(container)
|
||||
|
||||
inner = QVBoxLayout(container)
|
||||
inner.setContentsMargins(0, 0, 0, 0)
|
||||
inner.setSpacing(0)
|
||||
|
||||
inner.addWidget(_TitleBar(self, cfg))
|
||||
|
||||
self._text = QTextEdit()
|
||||
self._text.setReadOnly(True)
|
||||
self._text.setStyleSheet(f"""
|
||||
QTextEdit {{
|
||||
background-color: {cfg.bg_color};
|
||||
color: {cfg.fg_color};
|
||||
border: none;
|
||||
padding: 6px 8px;
|
||||
font-size: 13px;
|
||||
}}
|
||||
QScrollBar:vertical {{
|
||||
background: transparent;
|
||||
width: 4px;
|
||||
border: none;
|
||||
}}
|
||||
QScrollBar::handle:vertical {{
|
||||
background: {cfg.accent_color};
|
||||
border-radius: 2px;
|
||||
}}
|
||||
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {{
|
||||
height: 0;
|
||||
}}
|
||||
""")
|
||||
self._text.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
|
||||
inner.addWidget(self._text)
|
||||
|
||||
def _position_bottom_right(self) -> None:
|
||||
cfg = self._cfg
|
||||
screen = QApplication.primaryScreen().availableGeometry()
|
||||
x = screen.width() - cfg.width - cfg.margin_right
|
||||
y = screen.height() - cfg.height - cfg.margin_bottom
|
||||
self.move(x, y)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 公开 API
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def set_loading(self) -> None:
|
||||
self._sig_loading.emit()
|
||||
|
||||
def append_text(self, chunk: str) -> None:
|
||||
self._sig_append.emit(chunk)
|
||||
|
||||
def clear(self) -> None:
|
||||
self._sig_clear.emit()
|
||||
|
||||
def toggle(self) -> None:
|
||||
self._sig_toggle.emit()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 槽
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _on_loading(self) -> None:
|
||||
self._text.clear()
|
||||
self._text.setTextColor(QColor(self._cfg.accent_color))
|
||||
self._text.insertPlainText(_LOADING_TEXT)
|
||||
self._text.setTextColor(QColor(self._cfg.fg_color))
|
||||
self._on_show()
|
||||
|
||||
def _on_append(self, chunk: str) -> None:
|
||||
current = self._text.toPlainText()
|
||||
if current in (_LOADING_TEXT, _LOADING_TEXT + "\n"):
|
||||
self._text.clear()
|
||||
cursor = self._text.textCursor()
|
||||
cursor.movePosition(QTextCursor.MoveOperation.End)
|
||||
self._text.setTextCursor(cursor)
|
||||
self._text.insertPlainText(chunk)
|
||||
self._text.ensureCursorVisible()
|
||||
|
||||
def _on_clear(self) -> None:
|
||||
self._text.clear()
|
||||
|
||||
def _on_show(self) -> None:
|
||||
self.show()
|
||||
self.raise_()
|
||||
|
||||
def _on_hide(self) -> None:
|
||||
self.hide()
|
||||
|
||||
def _on_toggle(self) -> None:
|
||||
if self.isVisible():
|
||||
self.hide()
|
||||
else:
|
||||
self.show()
|
||||
self.raise_()
|
||||
|
||||
@property
|
||||
def app(self) -> QApplication:
|
||||
return self._app
|
||||
Reference in New Issue
Block a user