fix: opaque faces for floating windows and theme-aware pop-out icons
A transparent top-level renders black on the container's non-composited X11, so floating docks get a dynamic floating property feeding the QSS [floating="true"] opaque face, and PopoutWindow sets WA_StyledBackground. Pop-out titlebar glyphs are now painted in the palette's WindowText color and re-tint on PaletteChange, so they stay visible after a theme flip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user