SideFX Houdini customization package
git clone https://git.lucas.co/hou-control.git
hc: mask the split handles so their rounded corners are not black
border-radius in the stylesheet only limits where the background is painted;
the widget stays a rectangle. For an ordinary child widget that is harmless,
because it shares the parent's backing store and the unpainted corner pixels
are simply the parent's. These handles are WA_NativeWindow -- they have to be,
or X routes every click to the GL surface underneath -- and a native window has
its own surface with nothing of the parent in it, so the corners came out as
that surface's uninitialised black.
WA_TranslucentBackground is not the fix: X composites alpha for top-level
windows, and this is a child, so the corners would stay black. Masking is,
because it removes those pixels from the window rather than trying to blend
them; they stop being drawn and stop taking clicks. The mask is rebuilt in
resizeEvent, so it follows the geometry _pin already sets on every tick.
The style strings lose their border-radius at the same time. With the mask
doing the shaping, rounding the paint as well would antialias the curve against
the very surface these corners were showing, leaving a dark fringe just inside
the mask; a flat fill makes every pixel the mask keeps fully the handle's
colour. The curve is aliased now, which is the price of shaping a child window
that nothing composites.
Verified in a live session: the rebuilt handles report a non-empty mask with
all four corners outside it and the centre inside, so dragging is unaffected.
Co-Authored-By: Claude Opus 5 <[email protected]>
python3.13libs/hc/hcsplithandles.py | 41 ++++++++++++++++++++++++++++++++++---
1 file changed, 38 insertions(+), 3 deletions(-)
diff --git a/python3.13libs/hc/hcsplithandles.py b/python3.13libs/hc/hcsplithandles.py
index af795e8..4ebbac1 100644
--- a/python3.13libs/hc/hcsplithandles.py
+++ b/python3.13libs/hc/hcsplithandles.py
@@ -1,5 +1,6 @@
import hou
-from PySide6.QtCore import QEvent, QObject, QPoint, QRect, Qt, QTimer
+from PySide6.QtCore import QEvent, QObject, QPoint, QRect, QRectF, Qt, QTimer
+from PySide6.QtGui import QPainterPath, QRegion
from PySide6.QtWidgets import QWidget
@@ -19,8 +20,14 @@ HCSPLITHANDLE_TRACK_MS = 250
class _SplitHandle(QWidget):
- REST_STYLE = f"background: #3a3a3a; border-radius: {HCSPLITHANDLE_THICKNESS // 2}px;"
- HOVER_STYLE = f"background: #90EE90; border-radius: {HCSPLITHANDLE_THICKNESS // 2}px;"
+ # Flat fills, no border-radius: the rounded shape comes from the window
+ # mask in _applyMask, not from the paint. Rounding the paint as well would
+ # antialias the curve against the native surface behind it -- which is the
+ # black these corners used to be -- leaving a dark fringe just inside the
+ # mask. Filling the whole rect instead means every pixel the mask keeps is
+ # fully the handle's colour.
+ REST_STYLE = "background: #3a3a3a;"
+ HOVER_STYLE = "background: #90EE90;"
def __init__(self, parent, hou_pane, horizontal_boundary, manager):
super().__init__(parent)
@@ -70,6 +77,34 @@ class _SplitHandle(QWidget):
self.repaint()
return super().event(event)
+ def resizeEvent(self, event):
+ super().resizeEvent(event)
+ self._applyMask()
+
+ def _applyMask(self):
+ """Shape the window so the rounded corners are really absent.
+
+ border-radius in the stylesheet only limits where the background is
+ *painted*; the widget stays rectangular. For an ordinary child widget
+ that is harmless, because it shares the parent's backing store and the
+ unpainted corner pixels are simply the parent's. This handle is a
+ WA_NativeWindow (it has to be, or X routes every click to the GL
+ surface underneath -- see __init__), and a native window has its own
+ surface with nothing of the parent in it, so those corners came out as
+ the surface's uninitialised black.
+
+ WA_TranslucentBackground does not fix it: X composites alpha for
+ top-level windows, and this is a child. Masking does, because it
+ removes the corner pixels from the window itself rather than trying to
+ blend them -- they stop being drawn and stop taking clicks. The cost is
+ that a mask is a hard region, so the curve is aliased where the painted
+ background was smooth.
+ """
+ radius = HCSPLITHANDLE_THICKNESS / 2
+ path = QPainterPath()
+ path.addRoundedRect(QRectF(self.rect()), radius, radius)
+ self.setMask(QRegion(path.toFillPolygon().toPolygon()))
+
def mousePressEvent(self, event):
if event.button() != Qt.LeftButton:
return