git clone https://git.lucas.co/hou-control.git
python3.11libs/hc/hcsplithandles.py (7K)
1 import hou
2 from PySide6.QtCore import QEvent, QObject, QPoint, Qt, QTimer
3 from PySide6.QtWidgets import QWidget
4
5
6 HCSPLITHANDLE_THICKNESS = 20
7 HCSPLITHANDLE_LENGTH = 80
8 HCSPLITHANDLE_MIN_FRACTION = 0.05
9 HCSPLITHANDLE_MAX_FRACTION = 0.95
10
11
12 class _SplitHandle(QWidget):
13 REST_STYLE = f"background: #3a3a3a; border-radius: {HCSPLITHANDLE_THICKNESS // 2}px;"
14 HOVER_STYLE = f"background: #90EE90; border-radius: {HCSPLITHANDLE_THICKNESS // 2}px;"
15
16 def __init__(self, parent, hou_pane, horizontal_boundary, manager):
17 super().__init__(parent)
18 self.hou_pane = hou_pane
19 self.horizontal_boundary = horizontal_boundary
20 self.manager = manager
21 self.setObjectName(f"hc_split_handle_{hou_pane.id()}")
22 self.setCursor(Qt.SplitVCursor if horizontal_boundary else Qt.SplitHCursor)
23 self.setAttribute(Qt.WA_StyledBackground, True)
24 self.setStyleSheet(self.REST_STYLE)
25 self._origin = None
26 self._start_fraction = None
27 self._parent_extent = None
28 self._drag_child = None
29 self._pending_fraction = None
30 self._throttle = QTimer(self)
31 self._throttle.setSingleShot(True)
32 self._throttle.setInterval(33)
33 self._throttle.timeout.connect(self._applyPending)
34
35 def event(self, event):
36 et = event.type()
37 if et == QEvent.Enter:
38 self.setStyleSheet(self.HOVER_STYLE)
39 self.repaint()
40 elif et == QEvent.Leave:
41 self.setStyleSheet(self.REST_STYLE)
42 self.repaint()
43 return super().event(event)
44
45 def mousePressEvent(self, event):
46 if event.button() != Qt.LeftButton:
47 return
48 self._origin = event.globalPosition().toPoint()
49 self._drag_child = self.hou_pane.getSplitChild(0)
50 self._start_fraction = self._drag_child.getSplitFraction()
51 geom = self.hou_pane.qtScreenGeometry()
52 self._parent_extent = geom.height() if self.horizontal_boundary else geom.width()
53 event.accept()
54
55 def mouseMoveEvent(self, event):
56 if self._origin is None or not self._parent_extent:
57 return
58 delta = event.globalPosition().toPoint() - self._origin
59 pixel_delta = -delta.y() if self.horizontal_boundary else delta.x()
60 fraction_delta = pixel_delta / self._parent_extent
61 new_fraction = max(
62 HCSPLITHANDLE_MIN_FRACTION,
63 min(HCSPLITHANDLE_MAX_FRACTION, self._start_fraction + fraction_delta),
64 )
65 self._pending_fraction = new_fraction
66 if not self._throttle.isActive():
67 self._throttle.start()
68 event.accept()
69
70 def mouseReleaseEvent(self, event):
71 if self._pending_fraction is not None:
72 self._applyPending()
73 self._origin = None
74 self._start_fraction = None
75 self._parent_extent = None
76 self._drag_child = None
77 self._pending_fraction = None
78 self._throttle.stop()
79 self.manager._pin_all()
80
81 def _applyPending(self):
82 if self._pending_fraction is None or self._drag_child is None:
83 return
84 self._drag_child.setSplitFraction(self._pending_fraction)
85 self._pending_fraction = None
86 self.manager._pin(self)
87
88
89 class _ResizeFilter(QObject):
90 def __init__(self, manager):
91 super().__init__(manager.main)
92 self.manager = manager
93
94 def eventFilter(self, obj, event):
95 if event.type() == QEvent.Resize:
96 self.manager._pin_all()
97 return False
98
99
100 class HCSplitHandles:
101 def __init__(self):
102 self.main = hou.qt.mainWindow()
103 self.handles = []
104 self._filter = None
105
106 def show(self):
107 if self.handles:
108 return
109 for split in self._collectSplits():
110 orientation = self._orientation(split)
111 if orientation is None:
112 continue
113 handle = _SplitHandle(
114 self.main,
115 split,
116 horizontal_boundary=(orientation == "horizontal"),
117 manager=self,
118 )
119 handle.show()
120 handle.raise_()
121 self.handles.append(handle)
122 self._filter = _ResizeFilter(self)
123 self.main.installEventFilter(self._filter)
124 self._pin_all()
125
126 def hide(self):
127 for handle in self.handles:
128 handle.setParent(None)
129 handle.deleteLater()
130 self.handles = []
131 if self._filter is not None:
132 self.main.removeEventFilter(self._filter)
133 self._filter = None
134
135 def refresh(self):
136 self.hide()
137 self.show()
138
139 def isShowing(self):
140 return bool(self.handles)
141
142 def toggle(self):
143 if self.isShowing():
144 self.hide()
145 else:
146 self.show()
147
148 def _collectSplits(self):
149 splits = {}
150 for leaf in hou.ui.curDesktop().panes():
151 parent = leaf.getSplitParent()
152 while parent is not None:
153 splits[parent.id()] = parent
154 parent = parent.getSplitParent()
155 return splits.values()
156
157 def _orientation(self, split):
158 try:
159 c0 = split.getSplitChild(0)
160 c1 = split.getSplitChild(1)
161 except Exception:
162 return None
163 g0 = c0.qtScreenGeometry()
164 g1 = c1.qtScreenGeometry()
165 if g0.width() <= 0 or g0.height() <= 0 or g1.width() <= 0 or g1.height() <= 0:
166 return None
167 if abs(g0.y() - g1.y()) < 5 and g0.width() > 0:
168 return "vertical"
169 if abs(g0.x() - g1.x()) < 5 and g0.height() > 0:
170 return "horizontal"
171 return None
172
173 def _pin_all(self):
174 for handle in self.handles:
175 self._pin(handle)
176
177 def _pin(self, handle):
178 split = handle.hou_pane
179 try:
180 c0 = split.getSplitChild(0)
181 c1 = split.getSplitChild(1)
182 except Exception:
183 handle.hide()
184 return
185 g0 = c0.qtScreenGeometry()
186 g1 = c1.qtScreenGeometry()
187 if g0.width() <= 0 or g1.width() <= 0:
188 handle.hide()
189 return
190 split_geom = split.qtScreenGeometry()
191 if handle.horizontal_boundary:
192 y_screen = (g0.y() + g0.height() + g1.y()) // 2
193 x_screen = split_geom.x() + split_geom.width() // 2
194 center = self.main.mapFromGlobal(QPoint(x_screen, y_screen))
195 handle.setGeometry(
196 center.x() - HCSPLITHANDLE_LENGTH // 2,
197 center.y() - HCSPLITHANDLE_THICKNESS // 2,
198 HCSPLITHANDLE_LENGTH,
199 HCSPLITHANDLE_THICKNESS,
200 )
201 else:
202 x_screen = (g0.x() + g0.width() + g1.x()) // 2
203 y_screen = split_geom.y() + split_geom.height() // 2
204 center = self.main.mapFromGlobal(QPoint(x_screen, y_screen))
205 handle.setGeometry(
206 center.x() - HCSPLITHANDLE_THICKNESS // 2,
207 center.y() - HCSPLITHANDLE_LENGTH // 2,
208 HCSPLITHANDLE_THICKNESS,
209 HCSPLITHANDLE_LENGTH,
210 )
211 handle.show()
212 handle.raise_()