diff --git a/src/aare/gui/main_window.py b/src/aare/gui/main_window.py index c1314086..2f613ec9 100644 --- a/src/aare/gui/main_window.py +++ b/src/aare/gui/main_window.py @@ -2897,7 +2897,17 @@ class MainWindow(QMainWindow): @Slot(bool) def _on_dock_top_level_changed(self, floating: bool) -> None: dock = self.sender() - if floating and isinstance(dock, QDockWidget): + if not isinstance(dock, QDockWidget): + return + # Floating = top-level: a transparent top-level renders black on the + # container's non-composited X11, so the QDockWidget[floating="true"] + # QSS rule gives it an opaque face; docked it goes transparent again + # so the main-window gradient stays continuous. + dock.setProperty("floating", floating) + dock.style().unpolish(dock) + dock.style().polish(dock) + dock.update() + if floating: # Pop-outs open enlarged instead of keeping the cramped docked # size. Deferred: the window is mid-reparent while the signal # fires. diff --git a/src/aare/gui/widgets/popout_window.py b/src/aare/gui/widgets/popout_window.py index ace02255..40fd96b9 100644 --- a/src/aare/gui/widgets/popout_window.py +++ b/src/aare/gui/widgets/popout_window.py @@ -1,23 +1,24 @@ -from PySide6.QtCore import QPoint, QRect, QSize, Qt -from PySide6.QtGui import QCursor, QGuiApplication, QIcon, QPainter, QPen, QPixmap +from PySide6.QtCore import QEvent, QPoint, QRect, QSize, Qt +from PySide6.QtGui import QColor, QCursor, QGuiApplication, QIcon, QPainter, QPalette, QPen, QPixmap from PySide6.QtWidgets import QDockWidget, QHBoxLayout, QLabel, QToolButton, QVBoxLayout, QWidget -from aare.gui.styles import FRAME_L1_COLOR, FRAME_L1_WIDTH, TEXT, qcolor +from aare.gui.styles import FRAME_L1_COLOR, FRAME_L1_WIDTH, qcolor # Title-bar buttons: icon fills the button, both the same size. TITLEBAR_BUTTON_PX = 22 TITLEBAR_ICON_PX = 18 -def _titlebar_icon(kind: str, size: int = TITLEBAR_ICON_PX) -> QIcon: +def _titlebar_icon(kind: str, color: QColor, size: int = TITLEBAR_ICON_PX) -> QIcon: """Hand-painted borderless glyphs — the style's standard title-bar pixmaps draw boxed icons, and text glyphs are missing from the - container's fonts.""" + container's fonts. Color comes from the caller's palette so the glyphs + follow the theme (they are pixmaps, QSS color cannot reach them).""" pixmap = QPixmap(size, size) pixmap.fill(Qt.GlobalColor.transparent) painter = QPainter(pixmap) painter.setRenderHint(QPainter.RenderHint.Antialiasing) - pen = QPen(qcolor(TEXT), 2) + pen = QPen(color, 2) pen.setCapStyle(Qt.PenCapStyle.RoundCap) pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin) painter.setPen(pen) @@ -33,9 +34,9 @@ def _titlebar_icon(kind: str, size: int = TITLEBAR_ICON_PX) -> QIcon: return QIcon(pixmap) -def _titlebar_button(parent: QWidget, kind: str, tooltip: str) -> QToolButton: +def _titlebar_button(parent: QWidget, tooltip: str) -> QToolButton: + # Icon is set by DockTitleBar._tint_icons (initially and on theme change). button = QToolButton(parent) - button.setIcon(_titlebar_icon(kind)) button.setIconSize(QSize(TITLEBAR_ICON_PX, TITLEBAR_ICON_PX)) button.setFixedSize(TITLEBAR_BUTTON_PX, TITLEBAR_BUTTON_PX) button.setAutoRaise(True) @@ -66,15 +67,30 @@ class DockTitleBar(QWidget): layout.addStretch(1) self.popout_button = _titlebar_button( - self, "popout", "Open in a separate window (the panel stays here too)" + self, "Open in a separate window (the panel stays here too)" ) self.popout_button.clicked.connect(on_popout) layout.addWidget(self.popout_button) - close_button = _titlebar_button(self, "close", "Close panel (reopen via the View menu)") + close_button = _titlebar_button(self, "Close panel (reopen via the View menu)") close_button.clicked.connect(dock.close) layout.addWidget(close_button) + self._icon_buttons = {"popout": self.popout_button, "close": close_button} + self._tint_icons() + + def _tint_icons(self) -> None: + color = self.palette().color(QPalette.ColorRole.WindowText) + for kind, button in self._icon_buttons.items(): + button.setIcon(_titlebar_icon(kind, color)) + + def changeEvent(self, event): + # A theme switch lands here as a palette/style change; the glyphs are + # pixmaps, so they must be repainted in the new text color. + if event.type() in (QEvent.Type.PaletteChange, QEvent.Type.StyleChange): + self._tint_icons() + super().changeEvent(event) + class PopoutWindow(QWidget): """Additional top-level window for a panel mirror. @@ -98,6 +114,10 @@ class PopoutWindow(QWidget): def __init__(self, title: str, content: QWidget, parent=None): super().__init__(parent, Qt.WindowType.Window) + # QWidget SUBCLASSES skip QSS background painting unless this is set; + # an unpainted top-level renders black on the container's + # non-composited X11 (the PopoutWindow QSS rule supplies the fill). + self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True) self.setWindowTitle(title) self.setMinimumSize(300, 160) layout = QVBoxLayout(self) @@ -176,7 +196,7 @@ class PopoutWindow(QWidget): super().mousePressEvent(event) def mouseMoveEvent(self, event): - if self._manual_edges and self._press_global is not None and self._press_geom is not None: + if self._manual_edges and self._press_global is not None: delta = event.globalPosition().toPoint() - self._press_global geom = QRect(self._press_geom) if self._manual_edges & Qt.Edge.LeftEdge: @@ -201,3 +221,21 @@ class PopoutWindow(QWidget): self._press_global = None self._press_geom = None super().mouseReleaseEvent(event) + + +if __name__ == "__main__": + # ponytail: smallest check that fails if the edge maths breaks + import os + + os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") + from PySide6.QtWidgets import QApplication, QLabel + + app = QApplication([]) + w = PopoutWindow("t", QLabel("x")) + w.resize(400, 300) + assert w._edges_at(QPoint(5, 150)) == Qt.Edge.LeftEdge + assert w._edges_at(QPoint(398, 298)) == (Qt.Edge.RightEdge | Qt.Edge.BottomEdge) + assert w._edges_at(QPoint(200, 150)) == Qt.Edge(0) + 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 + print("gude")