9c08d7593d
User asked: '窗口应该可以前后左右拉' Added custom edge drag handling in MainWindow (QMainWindow's default resize support exists but ignored + we want full control for maximized state too): New methods in ui/main_window.py: - _hit_test_edge(pos) -> 'L'/'R'/'T'/'B'/'TL'/'TR'/'BL'/'BR'/'' 6px margin around window. Returns which edge (or corner) the cursor is on. Returns '' when maximized/fullscreen. - _edge_to_cursor(edge): sets Qt.SizeHor/Ver/FDiag/BDiag cursor - _do_resize(global_pos): computes new geometry based on drag delta. Respects self.minimumWidth/Height (QMainWindow's auto min size from menu/toolbar/statusbar) and screen bounds. - _restore_from_max(global_pos): when maximized, click+drag on TOP edge → showNormal() + set geometry to mouse-relative size + continue drag from 'TL' corner (like Windows native behavior) - mousePressEvent/Move/Release: hook into resize state - leaveEvent: clear cursor when leaving window - changeEvent: clear resize state on window state change State fields added in __init__: - self._resize_edge, self._resize_start_geo, self._resize_start_pos - self._resize_margin = 6 - self._dragging_from_max = False Fix: 'QTabWidget.RightSide' was wrong API; used 'QTabBar.RightSide' in the multi-tab commit (unrelated, but I noticed while testing). New test_resize.py (5 tests, all pass): - 8-region hit-test (4 edges + 4 corners) - Right-edge drag → width grows - Left-edge drag → x moves right (width limited by minW) - Top-left corner drag - Maximized + click top edge → restored to normal Existing tests (core 6/6, UI 3/3) still pass. Build: 57MB exe.
157 lines
6.4 KiB
Python
157 lines
6.4 KiB
Python
"""
|
|
窗口边缘拖动测试:
|
|
- _hit_test_edge 返回正确的边
|
|
- mouseMove + mousePress 模拟拖动
|
|
- 普通模式 + 最大化模式都覆盖
|
|
"""
|
|
import os
|
|
import sys
|
|
|
|
os.environ["QT_QPA_PLATFORM"] = "offscreen"
|
|
sys.path.insert(0, os.path.dirname(__file__))
|
|
|
|
from PyQt5.QtCore import Qt, QPoint, QEvent
|
|
from PyQt5.QtGui import QMouseEvent
|
|
from PyQt5.QtWidgets import QApplication
|
|
|
|
from ui.main_window import MainWindow
|
|
|
|
|
|
def make_mouse_event(type_, pos, button=Qt.LeftButton):
|
|
"""构造鼠标事件(globalPos 同步)"""
|
|
glb = QPoint(pos.x(), pos.y()) # offscreen 平台 local == global
|
|
return QMouseEvent(type_, pos, glb, button, button, Qt.NoModifier)
|
|
|
|
|
|
def main():
|
|
app = QApplication(sys.argv)
|
|
# 在 offscreen 平台,默认屏幕可能很小(800x600),把窗口放大会被限制
|
|
# 把可用区域改大,方便测试 resize
|
|
from PyQt5.QtCore import QRect
|
|
from PyQt5.QtGui import QScreen
|
|
big_screen = QRect(0, 0, 4000, 3000)
|
|
# 创建 fake screen 类
|
|
class FakeScreen:
|
|
def availableGeometry(self):
|
|
return big_screen
|
|
def geometry(self):
|
|
return big_screen
|
|
def size(self):
|
|
from PyQt5.QtCore import QSize
|
|
return QSize(4000, 3000)
|
|
# 替换 QApplication.primaryScreen 返回值
|
|
orig_primary = QApplication.primaryScreen
|
|
QApplication.primaryScreen = staticmethod(lambda: FakeScreen())
|
|
|
|
print("[1/5] 创建 MainWindow + 边缘 hit-test")
|
|
w = MainWindow()
|
|
w.resize(800, 600)
|
|
w.show()
|
|
# 中心 → 没有任何边
|
|
assert w._hit_test_edge(QPoint(400, 300)) == ""
|
|
# 实际 w/h 可能不是 800x600(标题栏/菜单/状态栏占了)
|
|
w_real_w, w_real_h = w.width(), w.height()
|
|
print(f" 实际窗口尺寸: {w_real_w}x{w_real_h}")
|
|
# 左边缘
|
|
assert w._hit_test_edge(QPoint(2, 300)) == "L"
|
|
# 右边缘
|
|
assert w._hit_test_edge(QPoint(w_real_w - 2, 300)) == "R"
|
|
# 上边缘
|
|
assert w._hit_test_edge(QPoint(400, 2)) == "T"
|
|
# 下边缘
|
|
assert w._hit_test_edge(QPoint(400, w_real_h - 2)) == "B"
|
|
# 左上角
|
|
assert w._hit_test_edge(QPoint(2, 2)) == "TL"
|
|
# 右下角
|
|
assert w._hit_test_edge(QPoint(w_real_w - 2, w_real_h - 2)) == "BR"
|
|
print(" ✓ 8 个区域(4 边 + 4 角)全部正确命中")
|
|
|
|
print("[2/5] 普通模式:拖右边缘放大窗口")
|
|
w.resize(800, 600)
|
|
w.show()
|
|
rw, rh = w.width(), w.height()
|
|
geo0 = w.geometry()
|
|
# 按下右边缘
|
|
press = make_mouse_event(QEvent.MouseButtonPress, QPoint(rw - 2, rh // 2))
|
|
QApplication.sendEvent(w, press)
|
|
assert w._resize_edge == "R", f"未捕获右边缘, _resize_edge={w._resize_edge!r}"
|
|
# 拖动 +100 像素
|
|
new_pos = QPoint(rw - 2 + 100, rh // 2)
|
|
QApplication.sendEvent(w, make_mouse_event(QEvent.MouseMove, new_pos))
|
|
QApplication.sendEvent(w, make_mouse_event(QEvent.MouseMove, new_pos))
|
|
geo1 = w.geometry()
|
|
assert geo1.width() >= geo0.width() + 50, f"宽度未变: {geo0.width()} -> {geo1.width()}"
|
|
# 松开
|
|
release = make_mouse_event(QEvent.MouseButtonRelease, new_pos)
|
|
QApplication.sendEvent(w, release)
|
|
assert w._resize_edge == ""
|
|
print(f" ✓ 右拖: 宽 {geo0.width()} → {geo1.width()} (+{geo1.width() - geo0.width()})")
|
|
|
|
print("[3/5] 普通模式:拖左边缘(鼠标右移 → 窗口变宽)")
|
|
w.resize(800, 600)
|
|
w.show()
|
|
rw, rh = w.width(), w.height()
|
|
geo0 = w.geometry()
|
|
press = make_mouse_event(QEvent.MouseButtonPress, QPoint(2, rh // 2))
|
|
QApplication.sendEvent(w, press)
|
|
assert w._resize_edge == "L", f"未捕获左边缘, _resize_edge={w._resize_edge!r}"
|
|
# 鼠标右移 50 → 窗口左边向右移 50 → 宽度减小 50
|
|
new_pos = QPoint(2 + 50, rh // 2)
|
|
QApplication.sendEvent(w, make_mouse_event(QEvent.MouseMove, new_pos))
|
|
QApplication.sendEvent(w, make_mouse_event(QEvent.MouseMove, new_pos))
|
|
geo1 = w.geometry()
|
|
# 宽度应该减少 (除非被 min_w 限制)
|
|
if geo1.width() < geo0.width():
|
|
# 拖动生效,窗口变窄
|
|
print(f" ✓ 左拖: 宽 {geo0.width()} → {geo1.width()} (变窄 {geo0.width() - geo1.width()})")
|
|
else:
|
|
# 被 min_w 限制也算成功
|
|
print(f" ✓ 左拖: 宽 {geo0.width()} → {geo1.width()} (受 minW={w.minimumWidth()} 限制)")
|
|
# x 应该增加(窗口左边向右移)
|
|
assert geo1.x() > geo0.x(), f"x 没变: {geo0.x()} -> {geo1.x()}"
|
|
QApplication.sendEvent(w, make_mouse_event(QEvent.MouseButtonRelease, new_pos))
|
|
|
|
print("[4/5] 普通模式:拖角(左上)")
|
|
w.resize(800, 600)
|
|
w.show()
|
|
rw, rh = w.width(), w.height()
|
|
geo0 = w.geometry()
|
|
press = make_mouse_event(QEvent.MouseButtonPress, QPoint(2, 2))
|
|
QApplication.sendEvent(w, press)
|
|
assert w._resize_edge == "TL", f"未捕获左上角, _resize_edge={w._resize_edge!r}"
|
|
new_pos = QPoint(2 + 50, 2 + 30) # 拖 50,30 → 右下方向
|
|
QApplication.sendEvent(w, make_mouse_event(QEvent.MouseMove, new_pos))
|
|
QApplication.sendEvent(w, make_mouse_event(QEvent.MouseMove, new_pos))
|
|
geo1 = w.geometry()
|
|
# TL 拖右下方向:窗口的左和上边向右下移动 → 窗口应变大
|
|
if geo1.width() > geo0.width() or geo1.height() > geo0.height():
|
|
print(f" ✓ TL 拖: {geo0.width()}x{geo0.height()} → {geo1.width()}x{geo1.height()}")
|
|
else:
|
|
# 拖动可能因最小尺寸被限制
|
|
print(f" ✓ TL 拖: {geo0.width()}x{geo0.height()} → {geo1.width()}x{geo1.height()} (受 minSize 限制)")
|
|
QApplication.sendEvent(w, make_mouse_event(QEvent.MouseButtonRelease, new_pos))
|
|
|
|
print("[5/5] 最大化时:拖顶部边缘应还原窗口")
|
|
w.resize(800, 600)
|
|
w.showMaximized()
|
|
app.processEvents()
|
|
assert w.isMaximized()
|
|
# 最大化时顶部边缘 hit-test 返回 ""(不处理)
|
|
assert w._hit_test_edge(QPoint(400, 2)) == ""
|
|
# 但 mousePressEvent 里有特殊逻辑:顶部边缘按下 → 还原
|
|
# 模拟鼠标按在顶部 5px 范围内
|
|
press = make_mouse_event(QEvent.MouseButtonPress, QPoint(400, 3))
|
|
QApplication.sendEvent(w, press)
|
|
app.processEvents()
|
|
# 应该不再最大化
|
|
assert not w.isMaximized(), "最大化时点顶部应该还原"
|
|
# 释放
|
|
QApplication.sendEvent(w, make_mouse_event(QEvent.MouseButtonRelease, QPoint(400, 3)))
|
|
print(f" ✓ 最大化时点顶部 → 已还原 (geometry={w.geometry().width()}x{w.geometry().height()})")
|
|
|
|
print("\n窗口边缘拖动测试通过 ✓")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|