Coverage for src / ezqt_app / utils / custom_grips.py: 83.23%
147 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-26 07:07 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-26 07:07 +0000
1# ///////////////////////////////////////////////////////////////
2# UTILS.CUSTOM_GRIPS - Custom resize grip widgets
3# Project: ezqt_app
4# ///////////////////////////////////////////////////////////////
6"""CustomGrip — window resize handles for frameless windows."""
8from __future__ import annotations
10# ///////////////////////////////////////////////////////////////
11# IMPORTS
12# ///////////////////////////////////////////////////////////////
13# Standard library imports
14from typing import Any
16# Third-party imports
17from PySide6.QtCore import QRect, Qt
18from PySide6.QtGui import QCursor, QMouseEvent
19from PySide6.QtWidgets import QFrame, QHBoxLayout, QSizeGrip, QWidget
22# ///////////////////////////////////////////////////////////////
23# CLASSES
24# ///////////////////////////////////////////////////////////////
25class CustomGrip(QWidget):
26 """
27 Custom resize widget for windows.
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 """
34 def __init__(
35 self, parent: QWidget, position: Qt.Edge, disable_color: bool = False
36 ) -> None:
37 """
38 Initialize the resize handle.
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
54 # Initialize internal members
55 self._container = None
56 self._left_grip = None
57 self._right_grip = None
58 self._center_grip = None
60 self.setObjectName(f"custom_grip_{str(position).split('.')[-1].lower()}")
61 self._setup_ui()
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()
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)
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)
85 layout = QHBoxLayout(self._container)
86 layout.setSpacing(0)
87 layout.setContentsMargins(0, 0, 0, 0)
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)
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[method-assign]
102 layout.addWidget(self._center_grip)
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)
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")
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)
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)
129 layout = QHBoxLayout(self._container)
130 layout.setSpacing(0)
131 layout.setContentsMargins(0, 0, 0, 0)
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)
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[method-assign]
146 layout.addWidget(self._center_grip)
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)
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")
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)
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[method-assign]
174 if self._disable_color:
175 self._center_grip.setStyleSheet("background: transparent")
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)
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[method-assign]
189 if self._disable_color:
190 self._center_grip.setStyleSheet("background: transparent")
192 # ///////////////////////////////////////////////////////////////
193 # RESIZE LOGIC
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()
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()
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()
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()
225 # ///////////////////////////////////////////////////////////////
226 # EVENT OVERRIDES
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)
238# ///////////////////////////////////////////////////////////////
239# PUBLIC API
240# ///////////////////////////////////////////////////////////////
241__all__ = ["CustomGrip"]