SideFX Houdini customization package
git clone https://git.lucas.co/hou-control.git
statusbar: give the circle its own X window so clicks reach it
Houdini draws its UI into one native GL window that owns mouse input
across its area; the circle, a plain child widget painted above it,
received no mouse or hover event at all. Same fix and same measurement
as the split handles: WA_NativeWindow plus an early winId(), with a
circular window mask so the native surface's corners stay hidden and
the ellipse painted edge to edge.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
python3.13libs/hc/hcstatusbar.py | 18 +++++++++++++++---
tools/check.py | 15 +++++++++++++++
2 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/python3.13libs/hc/hcstatusbar.py b/python3.13libs/hc/hcstatusbar.py
index 8a1fd33..7d0c335 100644
--- a/python3.13libs/hc/hcstatusbar.py
+++ b/python3.13libs/hc/hcstatusbar.py
@@ -21,7 +21,7 @@ import tempfile
import hou
import lxml.etree as ET
from PySide6.QtCore import QEvent, QObject, QPoint, Qt, QTimer
-from PySide6.QtGui import QColor, QFont, QPainter, QPen
+from PySide6.QtGui import QColor, QFont, QPainter, QRegion
from PySide6.QtWidgets import QFrame, QHBoxLayout, QWidget
@@ -123,6 +123,16 @@ class StatusCircle(QWidget):
self.setObjectName(self.OBJECT_NAME)
self.setFixedSize(HCSTATUSBAR_SIZE, HCSTATUSBAR_SIZE)
self.setCursor(Qt.PointingHandCursor)
+ # Its own X window, or it can be seen but never clicked: Houdini
+ # draws its UI into one native GL window that owns mouse input across
+ # its area, and a plain child widget stacked above only paints there.
+ # Same fix as hcsplithandles._SplitHandle, measured the same way --
+ # without it the circle received no mouse or hover event at all.
+ self.setAttribute(Qt.WA_NativeWindow, True)
+ self.winId()
+ # A native child has its own surface, so the corners outside the
+ # circle would be its uninitialised black; mask them away.
+ self.setMask(QRegion(self.rect(), QRegion.Ellipse))
self._state = None
self._minutes = 0
self._timer = QTimer(self)
@@ -155,8 +165,10 @@ class StatusCircle(QWidget):
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
- rect = self.rect().adjusted(2, 2, -2, -2)
- painter.setPen(QPen(QColor(0, 0, 0, 90), 1))
+ # Fill to the edge: the window mask is the circle's outline, and any
+ # pixel inside it left unpainted shows the native surface's black.
+ rect = self.rect()
+ painter.setPen(Qt.NoPen)
painter.setBrush(self.color())
painter.drawEllipse(rect)
if self._state == "autosave":
diff --git a/tools/check.py b/tools/check.py
index b118804..b2491ea 100644
--- a/tools/check.py
+++ b/tools/check.py
@@ -634,6 +634,21 @@ def check_status_circle():
check("menu XML filtered to runnable items", menu_documents_are_runnable)
+ def circle_is_a_native_window():
+ """Same lesson as the split handles: over Houdini's GL window a plain
+ child widget is painted but never clicked."""
+ from PySide6.QtCore import Qt
+ circle = hcstatusbar.StatusCircle()
+ try:
+ assert circle.testAttribute(Qt.WA_NativeWindow), "status circle is not a native window"
+ assert circle.internalWinId(), "native window was never created"
+ assert not circle.mask().isEmpty(), "no window mask; native corners would show black"
+ finally:
+ circle.deleteLater()
+ return "native, winId set, masked"
+
+ check("circle owns an X window", circle_is_a_native_window)
+
def check_geometry():
"""The visible-geometry walk, after HCNode was removed from under it."""