git.lucas.co / hou-control
SideFX Houdini customization package
git clone https://git.lucas.co/hou-control.git

commit406ececd8a31e6265b512b2445125af41fa5a11b
parentd9f77e44fc
authorLucas Galante <[email protected]>
date2026-09-11 14:14
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