Coverage for src / ezqt_app / utils / custom_grips.py: 83.23%

147 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-06 13:12 +0000

1# /////////////////////////////////////////////////////////////// 

2# UTILS.CUSTOM_GRIPS - Custom resize grip widgets 

3# Project: ezqt_app 

4# /////////////////////////////////////////////////////////////// 

5 

6"""CustomGrip — window resize handles for frameless windows.""" 

7 

8from __future__ import annotations 

9 

10# /////////////////////////////////////////////////////////////// 

11# IMPORTS 

12# /////////////////////////////////////////////////////////////// 

13# Standard library imports 

14from typing import Any 

15 

16# Third-party imports 

17from PySide6.QtCore import QRect, Qt 

18from PySide6.QtGui import QCursor, QMouseEvent 

19from PySide6.QtWidgets import QFrame, QHBoxLayout, QSizeGrip, QWidget 

20 

21 

22# /////////////////////////////////////////////////////////////// 

23# CLASSES 

24# /////////////////////////////////////////////////////////////// 

25class CustomGrip(QWidget): 

26 """ 

27 Custom resize widget for windows. 

28 

29 This class provides custom resize handles 

30 for different window edges (top, bottom, left, right). 

31 Each handle allows resizing the parent window. 

32 """ 

33 

34 def __init__( 

35 self, parent: QWidget, position: Qt.Edge, disable_color: bool = False 

36 ) -> None: 

37 """ 

38 Initialize the resize handle. 

39 

40 Parameters 

41 ---------- 

42 parent : QWidget 

43 The parent widget to resize. 

44 position : Qt.Edge 

45 The position of the handle (Qt.Edge.TopEdge, Qt.Edge.BottomEdge, etc.). 

46 disable_color : bool, optional 

47 Disable handle colors (default: False). 

48 """ 

49 super().__init__(parent) 

50 self._parent = parent 

51 self._position = position 

52 self._disable_color = disable_color 

53 

54 # Initialize internal members 

55 self._container = None 

56 self._left_grip = None 

57 self._right_grip = None 

58 self._center_grip = None 

59 

60 self.setObjectName(f"custom_grip_{str(position).split('.')[-1].lower()}") 

61 self._setup_ui() 

62 

63 def _setup_ui(self) -> None: 

64 """Configure the user interface based on position.""" 

65 if self._position == Qt.Edge.TopEdge: 

66 self._setup_top_grip() 

67 elif self._position == Qt.Edge.BottomEdge: 

68 self._setup_bottom_grip() 

69 elif self._position == Qt.Edge.LeftEdge: 

70 self._setup_left_grip() 

71 elif self._position == Qt.Edge.RightEdge: 71 ↛ exitline 71 didn't return from function '_setup_ui' because the condition on line 71 was always true

72 self._setup_right_grip() 

73 

74 def _setup_top_grip(self) -> None: 

75 """Configure the handle for the top edge.""" 

76 self.setGeometry(0, 0, self._parent.width(), 10) 

77 self.setMaximumHeight(10) 

78 

79 self._container = QFrame(self) 

80 self._container.setObjectName("grip_container_top") 

81 self._container.setGeometry(QRect(0, 0, self._parent.width(), 10)) 

82 self._container.setFrameShape(QFrame.Shape.NoFrame) 

83 self._container.setFrameShadow(QFrame.Shadow.Raised) 

84 

85 layout = QHBoxLayout(self._container) 

86 layout.setSpacing(0) 

87 layout.setContentsMargins(0, 0, 0, 0) 

88 

89 # Top Left 

90 self._left_frame = QFrame(self._container) 

91 self._left_frame.setObjectName("grip_top_left") 

92 self._left_frame.setFixedSize(10, 10) 

93 self._left_frame.setCursor(QCursor(Qt.CursorShape.SizeFDiagCursor)) 

94 self._left_grip = QSizeGrip(self._left_frame) 

95 layout.addWidget(self._left_frame) 

96 

97 # Top Center 

98 self._center_grip = QFrame(self._container) 

99 self._center_grip.setObjectName("grip_top_center") 

100 self._center_grip.setCursor(QCursor(Qt.CursorShape.SizeVerCursor)) 

101 self._center_grip.mouseMoveEvent = self._resize_top # type: ignore 

102 layout.addWidget(self._center_grip) 

103 

104 # Top Right 

105 self._right_frame = QFrame(self._container) 

106 self._right_frame.setObjectName("grip_top_right") 

107 self._right_frame.setFixedSize(10, 10) 

108 self._right_frame.setCursor(QCursor(Qt.CursorShape.SizeBDiagCursor)) 

109 self._right_grip = QSizeGrip(self._right_frame) 

110 layout.addWidget(self._right_frame) 

111 

112 if self._disable_color: 

113 self._container.setStyleSheet("background: transparent") 

114 self._left_frame.setStyleSheet("background: transparent") 

115 self._right_frame.setStyleSheet("background: transparent") 

116 self._center_grip.setStyleSheet("background: transparent") 

117 

118 def _setup_bottom_grip(self) -> None: 

119 """Configure the handle for the bottom edge.""" 

120 self.setGeometry(0, self._parent.height() - 10, self._parent.width(), 10) 

121 self.setMaximumHeight(10) 

122 

123 self._container = QFrame(self) 

124 self._container.setObjectName("grip_container_bottom") 

125 self._container.setGeometry(QRect(0, 0, self._parent.width(), 10)) 

126 self._container.setFrameShape(QFrame.Shape.NoFrame) 

127 self._container.setFrameShadow(QFrame.Shadow.Raised) 

128 

129 layout = QHBoxLayout(self._container) 

130 layout.setSpacing(0) 

131 layout.setContentsMargins(0, 0, 0, 0) 

132 

133 # Bottom Left 

134 self._left_frame = QFrame(self._container) 

135 self._left_frame.setObjectName("grip_bottom_left") 

136 self._left_frame.setFixedSize(10, 10) 

137 self._left_frame.setCursor(QCursor(Qt.CursorShape.SizeBDiagCursor)) 

138 self._left_grip = QSizeGrip(self._left_frame) 

139 layout.addWidget(self._left_frame) 

140 

141 # Bottom Center 

142 self._center_grip = QFrame(self._container) 

143 self._center_grip.setObjectName("grip_bottom_center") 

144 self._center_grip.setCursor(QCursor(Qt.CursorShape.SizeVerCursor)) 

145 self._center_grip.mouseMoveEvent = self._resize_bottom # type: ignore 

146 layout.addWidget(self._center_grip) 

147 

148 # Bottom Right 

149 self._right_frame = QFrame(self._container) 

150 self._right_frame.setObjectName("grip_bottom_right") 

151 self._right_frame.setFixedSize(10, 10) 

152 self._right_frame.setCursor(QCursor(Qt.CursorShape.SizeFDiagCursor)) 

153 self._right_grip = QSizeGrip(self._right_frame) 

154 layout.addWidget(self._right_frame) 

155 

156 if self._disable_color: 

157 self._container.setStyleSheet("background: transparent") 

158 self._left_frame.setStyleSheet("background: transparent") 

159 self._right_frame.setStyleSheet("background: transparent") 

160 self._center_grip.setStyleSheet("background: transparent") 

161 

162 def _setup_left_grip(self) -> None: 

163 """Configure the handle for the left edge.""" 

164 self.setGeometry(0, 10, 10, self._parent.height() - 20) 

165 self.setMaximumWidth(10) 

166 

167 self._center_grip = QFrame(self) 

168 self._center_grip.setObjectName("grip_left") 

169 self._center_grip.setCursor(QCursor(Qt.CursorShape.SizeHorCursor)) 

170 self._center_grip.setFrameShape(QFrame.Shape.NoFrame) 

171 self._center_grip.setFrameShadow(QFrame.Shadow.Raised) 

172 self._center_grip.mouseMoveEvent = self._resize_left # type: ignore 

173 

174 if self._disable_color: 

175 self._center_grip.setStyleSheet("background: transparent") 

176 

177 def _setup_right_grip(self) -> None: 

178 """Configure the handle for the right edge.""" 

179 self.setGeometry(self._parent.width() - 10, 10, 10, self._parent.height() - 20) 

180 self.setMaximumWidth(10) 

181 

182 self._center_grip = QFrame(self) 

183 self._center_grip.setObjectName("grip_right") 

184 self._center_grip.setCursor(QCursor(Qt.CursorShape.SizeHorCursor)) 

185 self._center_grip.setFrameShape(QFrame.Shape.NoFrame) 

186 self._center_grip.setFrameShadow(QFrame.Shadow.Raised) 

187 self._center_grip.mouseMoveEvent = self._resize_right # type: ignore 

188 

189 if self._disable_color: 

190 self._center_grip.setStyleSheet("background: transparent") 

191 

192 # /////////////////////////////////////////////////////////////// 

193 # RESIZE LOGIC 

194 

195 def _resize_top(self, event: QMouseEvent) -> None: 

196 delta = event.pos() 

197 height = max(self._parent.minimumHeight(), self._parent.height() - delta.y()) 

198 geo = self._parent.geometry() 

199 geo.setTop(geo.bottom() - height) 

200 self._parent.setGeometry(geo) 

201 event.accept() 

202 

203 def _resize_bottom(self, event: QMouseEvent) -> None: 

204 delta = event.pos() 

205 height = max(self._parent.minimumHeight(), self._parent.height() + delta.y()) 

206 self._parent.resize(self._parent.width(), height) 

207 event.accept() 

208 

209 def _resize_left(self, event: QMouseEvent) -> None: 

210 delta = event.pos() 

211 width = max(self._parent.minimumWidth(), self._parent.width() - delta.x()) 

212 geo = self._parent.geometry() 

213 geo.setLeft(geo.right() - width) 

214 self._parent.setGeometry(geo) 

215 event.accept() 

216 

217 def _resize_right(self, event: QMouseEvent) -> None: 

218 delta = event.pos() 

219 width = max(self._parent.minimumWidth(), delta.x()) 

220 geo = self._parent.geometry() 

221 geo.setWidth(width) 

222 self._parent.setGeometry(geo) 

223 event.accept() 

224 

225 # /////////////////////////////////////////////////////////////// 

226 # EVENT OVERRIDES 

227 

228 def resizeEvent(self, event: Any) -> None: 

229 """Handle widget resize by updating internal container geometry.""" 

230 if self._container: 230 ↛ 232line 230 didn't jump to line 232 because the condition on line 230 was always true

231 self._container.setGeometry(0, 0, self.width(), 10) 

232 elif self._center_grip: 

233 # For side grips, update the center frame height 

234 self._center_grip.setGeometry(0, 0, self.width(), self.height()) 

235 super().resizeEvent(event) 

236 

237 

238# /////////////////////////////////////////////////////////////// 

239# PUBLIC API 

240# /////////////////////////////////////////////////////////////// 

241__all__ = ["CustomGrip"]