"""PopoutWindow replaces dock floating: an additional top-level window whose edge band resizes, whose close only hides, and whose first show lands near the cursor. DockTitleBar puts the pop-out button next to the close box.""" from PySide6.QtCore import QEvent, QPoint, QPointF, Qt from PySide6.QtGui import QMouseEvent from PySide6.QtWidgets import QDockWidget, QLabel from aare.gui.widgets.popout_window import DockTitleBar, PopoutWindow def _window(qtbot): w = PopoutWindow("test", QLabel("content")) qtbot.addWidget(w) w.resize(400, 300) return w def test_edges_at(qtbot): w = _window(qtbot) band = PopoutWindow.RESIZE_MARGIN + PopoutWindow.OUTER_GRIP assert w._edges_at(QPoint(band - 1, 150)) == Qt.Edge.LeftEdge assert w._edges_at(QPoint(200, band - 1)) == Qt.Edge.TopEdge assert w._edges_at(QPoint(399, 299)) == (Qt.Edge.RightEdge | Qt.Edge.BottomEdge) assert w._edges_at(QPoint(200, 150)) == Qt.Edge(0) def test_cursor_for(qtbot): w = _window(qtbot) assert w._cursor_for(Qt.Edge.LeftEdge) == Qt.CursorShape.SizeHorCursor assert w._cursor_for(Qt.Edge.BottomEdge) == Qt.CursorShape.SizeVerCursor assert w._cursor_for(Qt.Edge.LeftEdge | Qt.Edge.TopEdge) == Qt.CursorShape.SizeFDiagCursor assert w._cursor_for(Qt.Edge.RightEdge | Qt.Edge.TopEdge) == Qt.CursorShape.SizeBDiagCursor assert w._cursor_for(Qt.Edge(0)) is None def test_close_hides_and_keeps_geometry(qtbot): w = _window(qtbot) w.show() qtbot.waitExposed(w) w.move(120, 130) geometry = w.geometry() w.close() assert not w.isVisible() # Reopen: the first-show placement must not run again (geometry kept). w.show() assert w._placed assert w.geometry() == geometry def test_manual_resize_fallback(qtbot): w = _window(qtbot) w.show() qtbot.waitExposed(w) w.resize(400, 300) start = w.geometry() grab = QPointF(399.0, 299.0) # bottom-right band press = QMouseEvent( QEvent.Type.MouseButtonPress, grab, w.mapToGlobal(grab.toPoint()).toPointF(), Qt.MouseButton.LeftButton, Qt.MouseButton.LeftButton, Qt.KeyboardModifier.NoModifier, ) w.mousePressEvent(press) if not w._manual_edges: # The platform accepted startSystemResize; the manual path is not # reachable here, and the press must not have moved the window. assert w.geometry() == start return move_to = grab + QPointF(40.0, 25.0) move = QMouseEvent( QEvent.Type.MouseMove, move_to, w.mapToGlobal(move_to.toPoint()).toPointF(), Qt.MouseButton.NoButton, Qt.MouseButton.LeftButton, Qt.KeyboardModifier.NoModifier, ) w.mouseMoveEvent(move) assert w.width() == start.width() + 40 assert w.height() == start.height() + 25 release = QMouseEvent( QEvent.Type.MouseButtonRelease, move_to, w.mapToGlobal(move_to.toPoint()).toPointF(), Qt.MouseButton.LeftButton, Qt.MouseButton.NoButton, Qt.KeyboardModifier.NoModifier, ) w.mouseReleaseEvent(release) assert not w._manual_edges def test_hover_sets_resize_cursor(qtbot): w = _window(qtbot) w.show() qtbot.waitExposed(w) def hover(pos): w.mouseMoveEvent( QMouseEvent( QEvent.Type.MouseMove, pos, w.mapToGlobal(pos.toPoint()).toPointF(), Qt.MouseButton.NoButton, Qt.MouseButton.NoButton, Qt.KeyboardModifier.NoModifier, ) ) hover(QPointF(2.0, 150.0)) assert w.cursor().shape() == Qt.CursorShape.SizeHorCursor hover(QPointF(200.0, 150.0)) assert w.cursor().shape() == Qt.CursorShape.ArrowCursor def test_dock_title_bar_buttons(qtbot): dock = QDockWidget("Console Log") qtbot.addWidget(dock) opened = [] bar = DockTitleBar(dock, on_popout=lambda: opened.append(True)) dock.setTitleBarWidget(bar) dock.show() qtbot.waitExposed(dock) qtbot.mouseClick(bar.popout_button, Qt.MouseButton.LeftButton) assert opened == [True] assert dock.isVisible() def test_manual_resize_from_top_left(qtbot): w = _window(qtbot) w.show() qtbot.waitExposed(w) w.resize(400, 300) # Drive the manual path directly; whether startSystemResize is available # is platform luck and the top/left arithmetic deserves coverage either way. w._manual_edges = Qt.Edge.LeftEdge | Qt.Edge.TopEdge w._press_global = w.mapToGlobal(QPoint(2, 2)) w._press_geom = w.geometry() move_to = QPointF(2.0 + 30.0, 2.0 + 20.0) w.mouseMoveEvent( QMouseEvent( QEvent.Type.MouseMove, move_to, w.mapToGlobal(move_to.toPoint()).toPointF(), Qt.MouseButton.NoButton, Qt.MouseButton.LeftButton, Qt.KeyboardModifier.NoModifier, ) ) assert w.width() == 400 - 30 assert w.height() == 300 - 20 def test_paint_border_knob(qtbot, monkeypatch): from PySide6.QtGui import QPixmap from aare.gui.widgets import popout_window as mod w = _window(qtbot) # Default FRAME_L1_WIDTH "0px": render must take the paint-nothing path. w.render(QPixmap(w.size())) # With a visible level-1 border the painter path runs. monkeypatch.setattr(mod, "FRAME_L1_WIDTH", "2px") w.render(QPixmap(w.size()))