feat: center cards in FlowLayout with equal margins

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
2025-08-28 14:53:14 +05:00
parent 60271f7a13
commit 57f6ac9c4b

View File

@@ -5,7 +5,7 @@ from PySide6.QtGui import QFont, QFontMetrics, QPainter
def compute_layout(nat_sizes, rect_width, spacing, max_scale): def compute_layout(nat_sizes, rect_width, spacing, max_scale):
""" """
Вычисляет расположение элементов с учетом отступов и возможного увеличения карточек. Вычисляет расположение элементов с учетом отступов и максимального масштабирования карточек.
nat_sizes: массив (N, 2) с натуральными размерами элементов (ширина, высота). nat_sizes: массив (N, 2) с натуральными размерами элементов (ширина, высота).
rect_width: доступная ширина контейнера. rect_width: доступная ширина контейнера.
spacing: отступ между элементами. spacing: отступ между элементами.
@@ -19,58 +19,63 @@ def compute_layout(nat_sizes, rect_width, spacing, max_scale):
result = np.zeros((N, 4), dtype=np.int32) result = np.zeros((N, 4), dtype=np.int32)
y = 0 y = 0
i = 0 i = 0
while i < N: while i < N:
sum_width = 0 sum_width = 0
row_max_height = 0 row_max_height = 0
count = 0 count = 0
j = i j = i
# Подбираем количество элементов для текущего ряда
# Подбираем количество элементов для текущего ряда с учетом max_scale
scaled_sizes = nat_sizes * max_scale
while j < N: while j < N:
w = nat_sizes[j, 0] w = scaled_sizes[j, 0]
# Если уже есть хотя бы один элемент и следующий не помещается с учетом spacing, выходим
if count > 0 and (sum_width + spacing + w) > rect_width: if count > 0 and (sum_width + spacing + w) > rect_width:
break break
sum_width += w sum_width += w
count += 1 count += 1
h = nat_sizes[j, 1] h = scaled_sizes[j, 1]
if h > row_max_height: if h > row_max_height:
row_max_height = h row_max_height = h
j += 1 j += 1
# Доступная ширина ряда с учетом обязательных отступов между элементами
available_width = rect_width - spacing * (count - 1) # Вычисляем общую ширину ряда включая отступы
desired_scale = available_width / sum_width if sum_width > 0 else 1.0 total_row_width = sum_width + spacing * (count - 1)
# Разрешаем увеличение карточек, но не более max_scale
scale = desired_scale if desired_scale < max_scale else max_scale # Вычисляем смещение для центрирования ряда
# Выравниваем по левому краю (offset = 0) x_offset = (rect_width - total_row_width) // 2
x = 0
# Размещаем элементы в ряду с центрированием
x = x_offset
for k in range(i, j): for k in range(i, j):
new_w = int(nat_sizes[k, 0] * scale) new_w = int(nat_sizes[k, 0] * max_scale)
new_h = int(nat_sizes[k, 1] * scale) new_h = int(nat_sizes[k, 1] * max_scale)
result[k, 0] = x result[k, 0] = x
result[k, 1] = y result[k, 1] = y
result[k, 2] = new_w result[k, 2] = new_w
result[k, 3] = new_h result[k, 3] = new_h
x += new_w + spacing x += new_w + spacing
y += int(row_max_height * scale) + spacing
y += int(row_max_height) + spacing
i = j i = j
return result, y return result, y
class FlowLayout(QLayout): class FlowLayout(QLayout):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.itemList = [] self.itemList = []
# Устанавливаем отступы контейнера в 0 и задаем spacing между карточками
self.setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0)
self._spacing = 3 # отступ между карточками self._spacing = 3
self._max_scale = 1.2 # максимальное увеличение карточек (например, на 20%) self._max_scale = 1.2
def addItem(self, item: QLayoutItem) -> None: def addItem(self, item: QLayoutItem) -> None:
self.itemList.append(item) self.itemList.append(item)
def takeAt(self, index: int) -> QLayoutItem: def takeAt(self, index: int) -> QLayoutItem:
if 0 <= index < len(self.itemList): if 0 <= index < len(self.itemList):
return self.itemList.pop(index) return self.itemList.pop(index)
raise IndexError("Index out of range") raise IndexError("Index out of range")
def count(self) -> int: def count(self) -> int:
return len(self.itemList) return len(self.itemList)
@@ -99,10 +104,12 @@ class FlowLayout(QLayout):
def minimumSize(self): def minimumSize(self):
size = QSize() size = QSize()
for item in self.itemList: for item in self.itemList:
size = size.expandedTo(item.minimumSize()) # Учитываем максимальный масштаб при расчете минимального размера
item_size = item.sizeHint()
scaled_size = QSize(int(item_size.width() * self._max_scale), int(item_size.height() * self._max_scale))
size = size.expandedTo(scaled_size)
margins = self.contentsMargins() margins = self.contentsMargins()
size += QSize(margins.left() + margins.right(), size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom())
margins.top() + margins.bottom())
return size return size
def doLayout(self, rect, testOnly): def doLayout(self, rect, testOnly):
@@ -110,14 +117,12 @@ class FlowLayout(QLayout):
if N == 0: if N == 0:
return 0 return 0
# Собираем натуральные размеры всех элементов в массив NumPy
nat_sizes = np.empty((N, 2), dtype=np.int32) nat_sizes = np.empty((N, 2), dtype=np.int32)
for i, item in enumerate(self.itemList): for i, item in enumerate(self.itemList):
s = item.sizeHint() s = item.sizeHint()
nat_sizes[i, 0] = s.width() nat_sizes[i, 0] = s.width()
nat_sizes[i, 1] = s.height() nat_sizes[i, 1] = s.height()
# Вычисляем геометрию с учетом spacing и max_scale через numba-функцию
geom_array, total_height = compute_layout(nat_sizes, rect.width(), self._spacing, self._max_scale) geom_array, total_height = compute_layout(nat_sizes, rect.width(), self._spacing, self._max_scale)
if not testOnly: if not testOnly: