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()))