from typing import Callable from PyQt5.QtCore import Qt, QRect, QPoint from PyQt5.QtWidgets import QSizePolicy from .flow_layout import FlowLayout class LibraryLayout(FlowLayout): def __init__(self, parent=None): super(LibraryLayout, self).__init__(parent) def expandingDirections(self) -> Qt.Orientations: return Qt.Orientations(Qt.Orientation(0)) # return Qt.Horizontal | Qt.Vertical def setGeometry(self, a0: QRect) -> None: super(FlowLayout, self).setGeometry(a0) self.doLayout(a0, False) def doLayout(self, rect, testonly): """! @brief Arranges the widgets for this layout
Layout +-----------------------------------------------+ | margin (above first row only) | |-----------------------------------------------| | vspace (hspace) | |-----------------------------------------------| | hpadding | |-----------------------------------------------| | _ _ __ +--------+ _ __ +--------+ _ __ _ | ||m||h|| h|| Widget ||h|| h|| Widget ||h|| h||m|| ||a||s|| p|| ||s|| p|| ||s|| p||a|| ||r||p|| a|| ||p|| a|| ||p|| a||r|| ||g||a|| d|| ||a|| d|| ||a|| d||g|| ||i||c|| d|| ||c|| d|| ||c|| d||i|| ||n||e|| i|| ||e|| i|| ||e|| i||n|| || || || n|| || || n|| || || n|| || || || || g|| || || g|| || || g|| || | - - -- +--------+ - -- +--------+ - -- - | |-----------------------------------------------| | vspace (hspace) | |-----------------------------------------------| | hpadding | |-----------------------------------------------| | margin (below last row only) | +-----------------------------------------------+ margin: doesn't play a role in the code below, it only affects the effective rectangle inside which we can layout hspace: static padding between widgets (minimum distance between them) hpadding: dynamic padding to fill the space when resizing@param self: object self-reference @param rect: the area the widgets should occupy @param testonly: only test the layout, don't arrange the items @return: the height of the layout """ left, top, right, bottom = self.getContentsMargins() effective = rect.adjusted(+left, +top, -right, -bottom) x = effective.x() y = effective.y() lineheight = 0 if not self._items: return y + lineheight - rect.y() + bottom widget = self._items[0].widget() hspace = self.horizontalSpacing() if hspace == -1: hspace = widget.style().layoutSpacing( QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal ) vspace = self.verticalSpacing() if vspace == -1: vspace = widget.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical) # lk: get the remaining space after subtracting the space required for each widget and its static padding # lk: also reserve space for the leading static padding rem_hspace = (effective.width() - hspace) % (widget.size().width() + hspace) # lk: the number of items and their static spacing that can be in the layout hspace_items = (effective.width() - hspace) // (widget.size().width() + hspace) # lk: in case the visible items are less than the maximum possible widgets visible_items = len([item for item in self._items if not item.isEmpty()]) if visible_items < hspace_items: hspace_items = visible_items rem_hspace = (effective.width() - hspace) - ((widget.size().width() + hspace) * hspace_items) try: # lk: the dynamic padding between each item, also account for the leading dynamic padding hpadding = rem_hspace // (hspace_items + 1) except ZeroDivisionError: hpadding = 0 for item in self._items: if item.isEmpty(): continue # lk: compute the location of the next widget next_x = x + item.sizeHint().width() + hspace + hpadding # lk: find out if there is enough space for the widget in this row # lk: account for the leading static and dynamic padding too (at the start) if next_x - hspace * 2 - hpadding * 2 > effective.right() and lineheight > 0: x = effective.x() # lk: find next vertical position, add static and dynamic padding y = y + lineheight + vspace + hpadding next_x = x + item.sizeHint().width() + hspace + hpadding lineheight = 0 # lk: add static and dynamic padding to the current widget x = x + hspace + hpadding if not testonly: item.setGeometry(QRect(QPoint(x, y), item.sizeHint())) x = next_x lineheight = max(lineheight, item.sizeHint().height()) return y + lineheight - rect.y() + bottom def sort(self, key: Callable, reverse=False) -> None: self._items.sort(key=key, reverse=reverse) self.setGeometry(self.parent().contentsRect().adjusted(*self.parent().getContentsMargins()))