Skip to content

Instantly share code, notes, and snippets.

@wmalarski
Last active October 25, 2019 15:20
Show Gist options
  • Save wmalarski/99814398c9fc8554e526a502a307714d to your computer and use it in GitHub Desktop.
Save wmalarski/99814398c9fc8554e526a502a307714d to your computer and use it in GitHub Desktop.
FrameLess resizing widget implementation using PySide2. https://forum.qt.io/topic/66586/make-a-frameless-qwidget-resizable-with-qrubberband/19
import sys
from enum import Enum, IntEnum
from typing import List
from PySide2.QtCore import QPropertyAnimation, QByteArray, Signal, QRect, Qt
from PySide2.QtGui import QResizeEvent, QColor, QMouseEvent
from PySide2.QtWidgets import QApplication, QWidget, QGridLayout, QLabel, QPushButton, \
QFrame, QToolButton, QHBoxLayout, QVBoxLayout, \
QGraphicsColorizeEffect, QStackedWidget, QButtonGroup, QAbstractButton, QToolBar, QMainWindow, QSizePolicy, \
QDockWidget
from tt1.frameless import FrameLess, Edge
class AppState(Enum):
State1 = "state1"
State2 = "state2"
class ExtensionState(IntEnum):
Settings = 0
EF = 1
ST = 2
class MyWidget(QFrame):
def __init__(self, parent=None):
super().__init__(parent)
self.setFrameStyle(QFrame.Box)
self.setStyleSheet("background: blue")
layout = QGridLayout(self)
self.setLayout(layout)
# text over dialog
header_label = QLabel(self.tr("I co?"))
layout.addWidget(header_label, 0, 0)
# project loading
load_button = QPushButton(self.tr("Dawaj kurwa ten projekt"), self)
layout.addWidget(load_button, 1, 0)
class SideBarWidget(QFrame):
page_selected = Signal(int)
page_deselected = Signal()
open_clicked = Signal()
def __init__(self, state, parent=None):
super().__init__(parent)
self.setFrameStyle(QFrame.Box)
self.setStyleSheet("background: green")
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
ver_layout = QVBoxLayout()
ver_layout.setContentsMargins(2, 2, 2, 2)
layout.addLayout(ver_layout)
# Stacked widget
self._stacked = QStackedWidget(self)
self._stacked_map = {}
self._state = state
ver_layout.addWidget(self._stacked)
# Side pane group
button_group = QButtonGroup(self)
button_group.setExclusive(False)
button_group.buttonClicked.connect(self._on_button_group_button_clicked)
button_group.buttonToggled.connect(self._on_button_group_button_toggled)
# Menu 1
self._menu1 = QWidget(self)
layout_menu1 = QVBoxLayout(self._menu1)
layout_menu1.setContentsMargins(0, 0, 0, 0)
self._menu1.setLayout(layout_menu1)
self._stacked.addWidget(self._menu1)
self._stacked_map[AppState.State1] = self._menu1
ef_button = QToolButton(self)
ef_button.setText(self.tr("EF"))
ef_button.setCheckable(True)
button_group.addButton(ef_button, ExtensionState.EF)
layout_menu1.addWidget(ef_button)
st_button = QToolButton(self)
st_button.setText(self.tr("ST"))
st_button.setCheckable(True)
button_group.addButton(st_button, ExtensionState.ST)
layout_menu1.addWidget(st_button)
# Menu 2
self._menu2 = QWidget(self)
layout_menu2 = QVBoxLayout(self._menu2)
layout_menu2.setContentsMargins(0, 0, 0, 0)
self._menu2.setLayout(layout_menu2)
self._stacked.addWidget(self._menu2)
self._stacked_map[AppState.State2] = self._menu2
# Settings button
self._settings_button = QToolButton(self)
self._settings_button.setText(self.tr("S"))
self._settings_button.setCheckable(True)
button_group.addButton(self._settings_button, ExtensionState.Settings)
ver_layout.addWidget(self._settings_button)
# Maximize button
self._maximize_button = QToolButton(self)
self._maximize_button.setText(">")
self._maximize_button.setCheckable(True)
self._maximize_button.clicked.connect(self.open_clicked)
layout.addWidget(self._maximize_button)
# updating state
self.state = state
@property
def state(self) -> AppState:
return self._state
@state.setter
def state(self, state: AppState):
self._state = state
self._stacked.setCurrentWidget(self._stacked_map[state])
def _on_button_group_button_clicked(self, button: QAbstractButton):
if button.isChecked():
for b in button.group().buttons():
if b != button:
b.setChecked(False)
def _on_button_group_button_toggled(self, button: QAbstractButton, toggled: bool):
if toggled:
self.page_selected.emit(button.group().id(button))
elif button.group().checkedButton() is None:
self.page_deselected.emit()
def expand(self, expanded: bool):
self._maximize_button.setText("<" if expanded else ">")
class ExtensionWidget(QFrame):
resize_finished = Signal(QRect)
def __init__(self, parent=None):
super().__init__(parent)
# self.setFrameStyle(QFrame.Box)
self.setStyleSheet("background: rgba(255, 0, 0, 100);")
self.setMinimumWidth(0)
self._frame = FrameLess(self)
self._frame.movable_edges = [Edge.Right]
self._frame.resize_finished.connect(self.resize_finished)
layout = QGridLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
self._stacked = QStackedWidget(self)
layout.addWidget(self._stacked, 0, 0)
self._animation = QPropertyAnimation(self, QByteArray(b"geometry"), parent=parent)
def set_animate_visible(self, visible: bool):
self._animation.setStartValue(self.geometry())
width = self._frame.geometry.width() if visible else self.minimumWidth()
self._animation.setEndValue(QRect(self.x(), self.y(), width, self.height()))
self._animation.start()
def add_widgets(self, widgets: List[QWidget]):
for widget in widgets:
self._stacked.addWidget(widget)
widget.setParent(self._stacked)
@property
def current_widget(self) -> QWidget:
return self._stacked.currentWidget()
@current_widget.setter
def current_widget(self, widget: QWidget):
self._stacked.setCurrentWidget(widget)
class MyToolBar(QToolBar):
toggled = Signal(bool)
def __init__(self, parent=None):
super().__init__(parent)
self.setStyleSheet("background: red")
self.setMovable(False)
self.layout().setContentsMargins(0, 0, 0, 0)
open_width = 200
close_width = 50
self._is_open = False
self.setMinimumWidth(close_width)
self._duration = 250
# close animation
self._animation_close = QPropertyAnimation(self, QByteArray(b"minimumWidth"), parent=parent)
self._animation_close.setDuration(self._duration)
self._animation_close.setStartValue(open_width)
self._animation_close.setEndValue(close_width)
# open animation
self._animation_open = QPropertyAnimation(self, QByteArray(b"minimumWidth"), parent=parent)
self._animation_open.setDuration(self._duration)
self._animation_open.setStartValue(close_width)
self._animation_open.setEndValue(open_width)
# extension widget
self._ext = ExtensionWidget(parent)
self._ext.setVisible(False)
def toggle(self):
if self._is_open:
self._animation_close.start()
else:
self._animation_open.start()
self._is_open = not self._is_open
self.toggled.emit(self._is_open)
def set_extension_visible(self, visible: bool):
self._ext.setVisible(True)
self._ext.raise_()
self._ext.set_animate_visible(visible)
def add_extension_widgets(self, widgets: List[QWidget]):
self._ext.add_widgets(widgets)
@property
def current_extension_widget(self) -> QWidget:
return self._ext.current_widget
@current_extension_widget.setter
def current_extension_widget(self, widget: QWidget):
self._ext.current_widget = widget
def resizeEvent(self, event: QResizeEvent):
super().resizeEvent(event)
self._ext.setGeometry(self.x() + self.width(), self.y(), self._ext.width(), event.size().height())
class InnerWidget(QFrame):
def __init__(self, text, parent=None):
super().__init__(parent)
self.setStyleSheet("QFrame{background: rgba(255, 0, 0, 100);}")
layout = QVBoxLayout(self)
self.setLayout(layout)
button = QPushButton(text, self)
layout.addWidget(button)
self.setMinimumWidth(200)
class DockWidget(QDockWidget):
def __init__(self, text, parent=None):
super().__init__(text, parent)
self.setStyleSheet("QWidget{background: purple;}")
# self.setAllowedAreas(Qt.RightDockWidgetArea | Qt.BottomDockWidgetArea | Qt.TopDockWidgetArea)
button = InnerWidget(text, self)
self.setWidget(button)
self.setMinimumWidth(200)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setMinimumSize(800, 600)
self._widget = MyWidget(self)
self.setCentralWidget(self._widget)
# Docks
self._docks = [DockWidget("dock1", self), DockWidget("dock2", self), DockWidget("dock3", self)]
for dock in self._docks:
self.addDockWidget(Qt.RightDockWidgetArea, dock)
# Tool Bar
self._tool_bar = MyToolBar(self)
# Side Bar
side_bar = SideBarWidget(AppState.State1, self)
side_bar.page_selected.connect(self._on_side_bar_page_selected)
side_bar.page_deselected.connect(self._on_side_bar_page_deselected)
self._tool_bar.addWidget(side_bar)
side_bar.open_clicked.connect(self._tool_bar.toggle)
self._tool_bar.toggled.connect(side_bar.expand)
self.addToolBar(Qt.LeftToolBarArea, self._tool_bar)
self.addToolBar(Qt.TopToolBarArea, QToolBar(self))
self._inner1 = InnerWidget("aa", self)
self._inner2 = InnerWidget("bb", self)
self._inner3 = InnerWidget("cc", self)
self._tool_bar.add_extension_widgets([self._inner1, self._inner2, self._inner3])
# Mapping
self._page_mapping = {
ExtensionState.EF: self._inner1,
ExtensionState.ST: self._inner2,
ExtensionState.Settings: self._inner3,
}
def _build_effect(self):
effect = QGraphicsColorizeEffect(self)
effect.setColor(QColor(0, 0, 0))
effect.setStrength(1.)
return effect
def _on_side_bar_page_selected(self, page: int):
widget = self._page_mapping[ExtensionState(page)]
self._tool_bar.current_extension_widget = widget
self._tool_bar.set_extension_visible(True)
self._widget.setEnabled(False)
for dock in self._docks:
dock.setEnabled(False)
dock.setFeatures(QDockWidget.NoDockWidgetFeatures)
dock.setGraphicsEffect(self._build_effect())
self._widget.setGraphicsEffect(self._build_effect())
def _on_side_bar_page_deselected(self):
self._tool_bar.set_extension_visible(False)
self._widget.setEnabled(True)
for dock in self._docks:
dock.setEnabled(True)
dock.setFeatures(QDockWidget.AllDockWidgetFeatures)
dock.setGraphicsEffect(None)
self._widget.setGraphicsEffect(None)
def main():
# Create the Qt Application
app = QApplication(sys.argv)
# Create and show the form
nav_widget = MainWindow()
nav_widget.show()
# Run the main Qt loop
sys.exit(app.exec_())
if __name__ == '__main__':
main()
from enum import IntEnum
from typing import List
from PySide2 import QtWidgets
from PySide2.QtCore import QObject, QPoint, Qt, QEvent, QMargins, QRect, Signal, QSize
from PySide2.QtGui import QHoverEvent, QMouseEvent
from PySide2.QtWidgets import QRubberBand, QWidget, QPushButton, QMainWindow, QSpinBox, QVBoxLayout
class Edge(IntEnum):
None_ = 0x0
Left = 0x1
Top = 0x2
Right = 0x4
Bottom = 0x8
TopLeft = Top | Left
TopRight = Top | Right
BottomLeft = Bottom | Left
BottomRight = Bottom | Right
class FrameLess(QObject):
resize_started = Signal(QRect)
resize_finished = Signal(QRect)
def __init__(self, parent: QWidget):
super().__init__(parent)
self._cursor_changed = False
self._left_button_pressed = False
self._border_width = 5
self._drag_pos = QPoint()
self._drag_start = False
self._limit_size = parent.maximumSize()
self._mouse_press_edge = Edge.None_
self._movable_edges = list(Edge)
parent.setMouseTracking(True)
parent.setWindowFlags(Qt.FramelessWindowHint)
parent.setAttribute(Qt.WA_Hover)
parent.installEventFilter(self)
self._rubber_band = QRubberBand(QRubberBand.Rectangle)
@property
def geometry(self) -> QRect:
return self._rubber_band.geometry()
@geometry.setter
def geometry(self, geometry):
self._rubber_band.setGeometry(geometry)
def eventFilter(self, watched: QObject, e: QEvent):
if e.type() == QEvent.MouseMove:
self._mouse_move(e)
elif e.type() == QEvent.HoverMove:
self._mouse_hover(e)
elif e.type() == QEvent.Leave:
self._mouse_leave(e)
elif e.type() == QEvent.MouseButtonPress:
self._mouse_press(e)
elif e.type() == QEvent.MouseButtonRelease:
self._mouse_release(e)
else:
return self.parent().eventFilter(watched, e)
return True
def _mouse_hover(self, e: QHoverEvent):
self._update_cursor_shape(self.parent().mapToParent(e.pos()))
def _mouse_leave(self, _: QEvent):
if not self._left_button_pressed:
self.parent().unsetCursor()
def _mouse_press(self, e: QMouseEvent):
if e.button() is Qt.LeftButton:
self._left_button_pressed = True
geometry = self.parent().frameGeometry()
self._mouse_press_edge = self._calculate_cursor_position(e.windowPos().toPoint(), geometry)
print(e.pos(), geometry, geometry.contains(e.pos()), self._mouse_press_edge)
if not (self._mouse_press_edge & Edge.None_):
self._rubber_band.setGeometry(self.parent().frameGeometry())
if self.parent().rect().marginsRemoved(QMargins(*([self._border_width] * 4))).contains(e.pos()):
self._drag_start = True
self._drag_pos = e.pos()
self.resize_started.emit(self._rubber_band.geometry())
def _mouse_release(self, e: QMouseEvent):
if e.button() is Qt.LeftButton:
self._left_button_pressed = False
self._drag_start = False
self.resize_finished.emit(self._rubber_band.geometry())
def _mouse_move(self, e: QMouseEvent):
if self._left_button_pressed and not self.parent().isMaximized():
if self._drag_start:
self.parent().move(self.parent().frameGeometry().topLeft() + (e.pos() - self._drag_pos))
if not self._mouse_press_edge & Edge.None_:
left = self._rubber_band.frameGeometry().left()
top = self._rubber_band.frameGeometry().top()
right = self._rubber_band.frameGeometry().right()
bottom = self._rubber_band.frameGeometry().bottom()
if self._mouse_press_edge & Edge.Top:
top = e.windowPos().y()
if self._mouse_press_edge & Edge.Bottom:
bottom = e.windowPos().y()
if self._mouse_press_edge & Edge.Left:
left = e.windowPos().x()
if self._mouse_press_edge & Edge.Right:
right = e.windowPos().x()
new_rect = QRect(QPoint(left, top), QPoint(right, bottom))
if new_rect.width() < self.parent().minimumWidth():
left = self.parent().frameGeometry().x()
if new_rect.height() < self.parent().minimumHeight():
top = self.parent().frameGeometry().y()
if new_rect.width() > self._limit_size.width():
right = self.parent().frameGeometry().x() + self._limit_size.width()
if new_rect.height() > self._limit_size.height():
bottom = self.parent().frameGeometry().y() + self._limit_size.height()
rect = QRect(QPoint(left, top), QPoint(right, bottom))
self.parent().setGeometry(rect)
self._rubber_band.setGeometry(rect)
else:
self._update_cursor_shape(e.pos())
def _update_cursor_shape(self, pos: QPoint):
if self.parent().isFullScreen() or self.parent().isMaximized():
if self._cursor_changed:
self.parent().unsetCursor()
return
if not self._left_button_pressed:
geometry = self.parent().frameGeometry()
mouse_move = self._calculate_cursor_position(pos, geometry)
self._cursor_changed = True
if mouse_move == Edge.TopLeft or mouse_move == Edge.BottomRight:
self.parent().setCursor(Qt.SizeFDiagCursor)
elif mouse_move == Edge.TopRight or mouse_move == Edge.BottomLeft:
self.parent().setCursor(Qt.SizeBDiagCursor)
elif mouse_move == Edge.Top or mouse_move == Edge.Bottom:
self.parent().setCursor(Qt.SizeVerCursor)
elif mouse_move == Edge.Left or mouse_move == Edge.Right:
self.parent().setCursor(Qt.SizeHorCursor)
elif self._cursor_changed:
self.parent().unsetCursor()
self._cursor_changed = False
def _calculate_cursor_position(self, pos: QPoint, rect: QRect):
frame_width = self._border_width
top = rect.y() + frame_width
left = rect.x() + frame_width
bottom = rect.y() + rect.height() - frame_width
right = rect.x() + rect.width() - frame_width
if rect.x() - frame_width <= pos.x() <= left and bottom >= pos.y() >= top:
edge = Edge.Left
elif right <= pos.x() <= rect.x() + rect.width() and top <= pos.y() <= bottom:
edge = Edge.Right
elif left <= pos.x() <= right and bottom <= pos.y() <= rect.y() + rect.height():
edge = Edge.Bottom
elif left <= pos.x() <= right and rect.y() <= pos.y() <= top:
edge = Edge.Top
elif left >= pos.x() >= rect.x() and rect.y() + rect.height() >= pos.y() >= bottom:
edge = Edge.BottomLeft
elif right <= pos.x() <= rect.x() + rect.width() and bottom <= pos.y() <= rect.y() + rect.height():
edge = Edge.BottomRight
elif right <= pos.x() <= rect.x() + rect.width() and rect.y() <= pos.y() <= top:
edge = Edge.TopRight
elif rect.x() <= pos.x() <= left and rect.y() <= pos.y() <= top:
edge = Edge.TopLeft
else:
edge = Edge.None_
print(pos, rect, edge, edge if edge in self._movable_edges else Edge.None_)
return edge if edge in self._movable_edges else Edge.None_
@property
def border_width(self) -> int:
return self._border_width
@border_width.setter
def border_width(self, border_width: int):
self._border_width = border_width
@property
def movable_edges(self) -> List[Edge]:
return self._movable_edges
@movable_edges.setter
def movable_edges(self, movable_edges: List[Edge]):
self._movable_edges = movable_edges
@property
def limit_size(self) -> QSize:
return self._limit_size
@limit_size.setter
def limit_size(self, limit_size: QSize):
self._limit_size = limit_size
class MyMainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self._frame = FrameLess(self)
# self._frame.limit_size = QSize(600, 600)
widget = QWidget(self)
self.setCentralWidget(widget)
layout = QVBoxLayout(widget)
widget.setLayout(layout)
button = QPushButton(self.tr("Click!"), self)
button.clicked.connect(self._on_button_clicked)
layout.addWidget(button)
maximize = QPushButton(self.tr("Maximize!"), self)
maximize.clicked.connect(self._on_maximize_clicked)
layout.addWidget(maximize)
spin = QSpinBox(self)
spin.setMinimum(1)
spin.setMaximum(25)
spin.setValue(self._frame.border_width)
spin.valueChanged.connect(self._on_spin_value_changed)
layout.addWidget(spin)
def _on_button_clicked(self):
self.close()
def _on_maximize_clicked(self):
if self.isMaximized():
self.showNormal()
else:
self.showMaximized()
def _on_spin_value_changed(self, value: int):
self._frame.border_width = value
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
widget = MyMainWindow()
widget.setMinimumWidth(100)
widget.setMinimumHeight(100)
widget.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
from enum import IntEnum
from typing import List
from PySide2 import QtWidgets
from PySide2.QtCore import QObject, QPoint, Qt, QEvent, QMargins, QRect, Signal, QSize
from PySide2.QtGui import QHoverEvent, QMouseEvent
from PySide2.QtWidgets import QRubberBand, QWidget, QPushButton, QMainWindow, QSpinBox, QVBoxLayout
class Edge(IntEnum):
None_ = 0x0
Left = 0x1
Top = 0x2
Right = 0x4
Bottom = 0x8
TopLeft = Top | Left
TopRight = Top | Right
BottomLeft = Bottom | Left
BottomRight = Bottom | Right
class FrameLess(QObject):
resize_started = Signal(QRect)
resize_finished = Signal(QRect)
def __init__(self, parent: QWidget):
super().__init__(parent)
self._cursor_changed = False
self._left_button_pressed = False
self._border_width = 5
self._drag_pos = QPoint()
self._drag_start = False
self._limit_size = parent.maximumSize()
self._mouse_press_edge = Edge.None_
self._movable_edges = list(Edge)
parent.setMouseTracking(True)
parent.setWindowFlags(Qt.FramelessWindowHint)
parent.setAttribute(Qt.WA_Hover)
parent.installEventFilter(self)
self._rubber_band = QRubberBand(QRubberBand.Rectangle)
@property
def geometry(self) -> QRect:
return self._rubber_band.geometry()
def eventFilter(self, watched: QObject, e: QEvent):
if e.type() == QEvent.MouseMove:
self._mouse_move(e)
elif e.type() == QEvent.HoverMove:
self._mouse_hover(e)
elif e.type() == QEvent.Leave:
self._mouse_leave(e)
elif e.type() == QEvent.MouseButtonPress:
self._mouse_press(e)
elif e.type() == QEvent.MouseButtonRelease:
self._mouse_release(e)
else:
return self.parent().eventFilter(watched, e)
return True
def _mouse_hover(self, e: QHoverEvent):
self._update_cursor_shape(self.parent().mapToGlobal(e.pos()))
def _mouse_leave(self, _: QEvent):
if not self._left_button_pressed:
self.parent().unsetCursor()
def _mouse_press(self, e: QMouseEvent):
if e.button() is Qt.LeftButton:
self._left_button_pressed = True
geometry = self.parent().frameGeometry()
self._mouse_press_edge = self._calculate_cursor_position(e.globalPos(), geometry)
if not (self._mouse_press_edge & Edge.None_):
self._rubber_band.setGeometry(self.parent().frameGeometry())
if self.parent().rect().marginsRemoved(QMargins(*([self._border_width] * 4))).contains(e.pos()):
self._drag_start = True
self._drag_pos = e.pos()
self.resize_started.emit(self._rubber_band.geometry())
def _mouse_release(self, e: QMouseEvent):
if e.button() is Qt.LeftButton:
self._left_button_pressed = False
self._drag_start = False
self.resize_finished.emit(self._rubber_band.geometry())
def _mouse_move(self, e: QMouseEvent):
if self._left_button_pressed and not self.parent().isMaximized():
if self._drag_start:
self.parent().move(self.parent().frameGeometry().topLeft() + (e.pos() - self._drag_pos))
if not self._mouse_press_edge & Edge.None_:
left = self._rubber_band.frameGeometry().left()
top = self._rubber_band.frameGeometry().top()
right = self._rubber_band.frameGeometry().right()
bottom = self._rubber_band.frameGeometry().bottom()
if self._mouse_press_edge & Edge.Top:
top = e.globalPos().y()
if self._mouse_press_edge & Edge.Bottom:
bottom = e.globalPos().y()
if self._mouse_press_edge & Edge.Left:
left = e.globalPos().x()
if self._mouse_press_edge & Edge.Right:
right = e.globalPos().x()
new_rect = QRect(QPoint(left, top), QPoint(right, bottom))
if new_rect.width() < self.parent().minimumWidth():
left = self.parent().frameGeometry().x()
if new_rect.height() < self.parent().minimumHeight():
top = self.parent().frameGeometry().y()
if new_rect.width() > self._limit_size.width():
right = self.parent().frameGeometry().x() + self._limit_size.width()
if new_rect.height() > self._limit_size.height():
bottom = self.parent().frameGeometry().y() + self._limit_size.height()
self.parent().setGeometry(QRect(QPoint(left, top), QPoint(right, bottom)))
self._rubber_band.setGeometry(QRect(QPoint(left, top), QPoint(right, bottom)))
else:
self._update_cursor_shape(e.globalPos())
def _update_cursor_shape(self, pos: QPoint):
if self.parent().isFullScreen() or self.parent().isMaximized():
if self._cursor_changed:
self.parent().unsetCursor()
return
if not self._left_button_pressed:
mouse_move = self._calculate_cursor_position(pos, self.parent().frameGeometry())
self._cursor_changed = True
if mouse_move == Edge.TopLeft or mouse_move == Edge.BottomRight:
self.parent().setCursor(Qt.SizeFDiagCursor)
elif mouse_move == Edge.TopRight or mouse_move == Edge.BottomLeft:
self.parent().setCursor(Qt.SizeBDiagCursor)
elif mouse_move == Edge.Top or mouse_move == Edge.Bottom:
self.parent().setCursor(Qt.SizeVerCursor)
elif mouse_move == Edge.Left or mouse_move == Edge.Right:
self.parent().setCursor(Qt.SizeHorCursor)
elif self._cursor_changed:
self.parent().unsetCursor()
self._cursor_changed = False
def _calculate_cursor_position(self, pos: QPoint, rect: QRect):
frame_width = self._border_width
top = rect.y() + frame_width
left = rect.x() + frame_width
bottom = rect.y() + rect.height() - frame_width
right = rect.x() + rect.width() - frame_width
if rect.x() - frame_width <= pos.x() <= left and bottom >= pos.y() >= top:
edge = Edge.Left
elif right <= pos.x() <= rect.x() + rect.width() and top <= pos.y() <= bottom:
edge = Edge.Right
elif left <= pos.x() <= right and bottom <= pos.y() <= rect.y() + rect.height():
edge = Edge.Bottom
elif left <= pos.x() <= right and rect.y() <= pos.y() <= top:
edge = Edge.Top
elif left >= pos.x() >= rect.x() and rect.y() + rect.height() >= pos.y() >= bottom:
edge = Edge.BottomLeft
elif right <= pos.x() <= rect.x() + rect.width() and bottom <= pos.y() <= rect.y() + rect.height():
edge = Edge.BottomRight
elif right <= pos.x() <= rect.x() + rect.width() and rect.y() <= pos.y() <= top:
edge = Edge.TopRight
elif rect.x() <= pos.x() <= left and rect.y() <= pos.y() <= top:
edge = Edge.TopLeft
else:
edge = Edge.None_
return edge if edge in self._movable_edges else Edge.None_
@property
def border_width(self) -> int:
return self._border_width
@border_width.setter
def border_width(self, border_width: int):
self._border_width = border_width
@property
def movable_edges(self) -> List[Edge]:
return self._movable_edges
@movable_edges.setter
def movable_edges(self, movable_edges: List[Edge]):
self._movable_edges = movable_edges
@property
def limit_size(self) -> QSize:
return self._limit_size
@limit_size.setter
def limit_size(self, limit_size: QSize):
self._limit_size = limit_size
class MyMainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self._frame = FrameLess(self)
# self._frame.limit_size = QSize(600, 600)
widget = QWidget(self)
self.setCentralWidget(widget)
layout = QVBoxLayout(widget)
widget.setLayout(layout)
button = QPushButton(self.tr("Click!"), self)
button.clicked.connect(self._on_button_clicked)
layout.addWidget(button)
maximize = QPushButton(self.tr("Maximize!"), self)
maximize.clicked.connect(self._on_maximize_clicked)
layout.addWidget(maximize)
spin = QSpinBox(self)
spin.setMinimum(1)
spin.setMaximum(25)
spin.setValue(self._frame.border_width)
spin.valueChanged.connect(self._on_spin_value_changed)
layout.addWidget(spin)
def _on_button_clicked(self):
self.close()
def _on_maximize_clicked(self):
if self.isMaximized():
self.showNormal()
else:
self.showMaximized()
def _on_spin_value_changed(self, value: int):
self._frame.border_width = value
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
widget = MyMainWindow()
widget.setMinimumWidth(100)
widget.setMinimumHeight(100)
widget.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment