Skip to content

Instantly share code, notes, and snippets.

@rafalstapinski
Created September 30, 2022 18:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rafalstapinski/496ce41724e19f4c513e4a810dbafb00 to your computer and use it in GitHub Desktop.
Save rafalstapinski/496ce41724e19f4c513e4a810dbafb00 to your computer and use it in GitHub Desktop.
A custom Qt FlowLayout that efficiently reorganizes elements when the viewport changes.
import math
from PySide6 import QtWidgets, QtCore
class FlowLayout(QtWidgets.QLayout):
item_list: list[QtWidgets.QLayoutItem]
horizontal_space: int
vertical_space: int
margin: int
current_size: tuple[int, int]
item_width: int
item_height: int
def __init__(self, parent: QtWidgets.QWidget, item_width: int, item_height: int):
super().__init__(parent)
if parent is not None:
self.setContentsMargins(0, 0, 0, 0)
# spaces between each item
self.horizontal_space = 5
self.vertical_space = 5
self.item_width = item_width
self.item_height = item_height
self.item_list = []
self.current_size = (0, 0)
def __del__(self):
item = self.takeAt(0)
while item:
item = self.takeAt(0)
def addItem(self, item):
self.item_list.append(item)
def count(self) -> int:
return len(self.item_list)
def itemAt(self, index) -> QtWidgets.QLayoutItem | None:
if index >= 0 and index < len(self.item_list):
return self.item_list[index]
return None
def takeAt(self, index: int) -> QtWidgets.QLayoutItem | None:
if index >= 0 and index < len(self.item_list):
return self.item_list.pop(index)
return None
def addWidget(self, widget: QtWidgets.QWidget):
super().addWidget(widget)
def expandingDirections(self) -> QtCore.Qt.Orientations:
return QtCore.Qt.Orientations(QtCore.Qt.Orientation(0))
def hasHeightForWidth(self) -> bool:
return True
def heightForWidth(self, width: int) -> int:
if width == 0:
return -1
# TODO take into account the right most horizontal_space
column_count = max([width // self.item_width, 1])
row_count = math.ceil(len(self.item_list) / column_count)
height = row_count * (self.item_height + self.horizontal_space)
# if even, remove last spacing
if len(self.item_list) % 2 == 0:
height -= self.vertical_space
return height
def setGeometry(self, rect: QtCore.QRect):
super().setGeometry(rect)
self.doLayout(rect)
def sizeHint(self) -> int:
return self.minimumSize()
def minimumSize(self) -> int:
size = QtCore.QSize()
for item in self.item_list:
size = size.expandedTo(item.minimumSize())
return size
def doLayout(self, rect: QtCore.QRect):
if (rect.width(), rect.height()) == self.current_size:
return
self.current_size = (rect.width(), rect.height())
column_count = max([rect.width() // self.item_width, 1])
centering_offset = (rect.width() - (column_count * self.item_width)) // 2
row = 0
column = 0
for item in self.item_list:
x_offset = column * self.item_width + centering_offset
y_offset = row * self.item_height
column += 1
if column == column_count:
column = 0
row += 1
item.setGeometry(QtCore.QRect(x_offset, y_offset, self.item_width, self.item_height))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment