fix: scale animation is less unstable
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
@@ -5,11 +5,11 @@ 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: отступ между элементами (горизонтальный и вертикальный).
|
||||||
max_scale: максимальный коэффициент масштабирования (например, 1.2).
|
max_scale: максимальный коэффициент масштабирования (например, 1.0).
|
||||||
|
|
||||||
Возвращает:
|
Возвращает:
|
||||||
result: массив (N, 4), где каждая строка содержит [x, y, new_width, new_height].
|
result: массив (N, 4), где каждая строка содержит [x, y, new_width, new_height].
|
||||||
@@ -19,55 +19,81 @@ 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
|
||||||
|
min_margin = 20 # Минимальный отступ по краям
|
||||||
|
|
||||||
|
# Определяем максимальное количество элементов в ряду и общий масштаб
|
||||||
|
max_items_per_row = 0
|
||||||
|
global_scale = 1.0
|
||||||
|
temp_i = 0
|
||||||
|
|
||||||
|
# Первый проход: находим максимальное количество элементов в ряду
|
||||||
|
while temp_i < N:
|
||||||
|
sum_width = 0
|
||||||
|
count = 0
|
||||||
|
temp_j = temp_i
|
||||||
|
while temp_j < N:
|
||||||
|
w = nat_sizes[temp_j, 0]
|
||||||
|
if count > 0 and (sum_width + spacing + w) > rect_width - 2 * min_margin:
|
||||||
|
break
|
||||||
|
sum_width += w
|
||||||
|
count += 1
|
||||||
|
temp_j += 1
|
||||||
|
|
||||||
|
if count > max_items_per_row:
|
||||||
|
max_items_per_row = count
|
||||||
|
# Вычисляем масштаб для самого заполненного ряда
|
||||||
|
available_width = rect_width - spacing * (count - 1) - 2 * min_margin
|
||||||
|
desired_scale = available_width / sum_width if sum_width > 0 else 1.0
|
||||||
|
global_scale = desired_scale if desired_scale < max_scale else max_scale
|
||||||
|
|
||||||
|
temp_i = temp_j
|
||||||
|
|
||||||
|
# Второй проход: размещаем элементы
|
||||||
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 = scaled_sizes[j, 0]
|
w = nat_sizes[j, 0]
|
||||||
if count > 0 and (sum_width + spacing + w) > rect_width:
|
if count > 0 and (sum_width + spacing + w) > rect_width - 2 * min_margin:
|
||||||
break
|
break
|
||||||
sum_width += w
|
sum_width += w
|
||||||
count += 1
|
count += 1
|
||||||
h = scaled_sizes[j, 1]
|
h = nat_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
|
||||||
|
|
||||||
# Вычисляем общую ширину ряда включая отступы
|
# Используем глобальный масштаб для всех рядов
|
||||||
total_row_width = sum_width + spacing * (count - 1)
|
scale = global_scale
|
||||||
|
scaled_row_width = int(sum_width * scale) + spacing * (count - 1)
|
||||||
|
|
||||||
# Вычисляем смещение для центрирования ряда
|
# Центрируем ряд относительно контейнера
|
||||||
x_offset = (rect_width - total_row_width) // 2
|
x = max(min_margin, (rect_width - scaled_row_width) // 2)
|
||||||
|
|
||||||
# Размещаем элементы в ряду с центрированием
|
|
||||||
x = x_offset
|
|
||||||
for k in range(i, j):
|
for k in range(i, j):
|
||||||
new_w = int(nat_sizes[k, 0] * max_scale)
|
new_w = int(nat_sizes[k, 0] * scale)
|
||||||
new_h = int(nat_sizes[k, 1] * max_scale)
|
new_h = int(nat_sizes[k, 1] * 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) + spacing
|
y += int(row_max_height * scale) + 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 = []
|
||||||
self.setContentsMargins(0, 0, 0, 0)
|
self.setContentsMargins(10, 10, 10, 10)
|
||||||
self._spacing = 3
|
self._spacing = 20 # Отступ для анимации и предотвращения перекрытий
|
||||||
self._max_scale = 1.2
|
self._max_scale = 1.0 # Отключено масштабирование в layout
|
||||||
|
|
||||||
def addItem(self, item: QLayoutItem) -> None:
|
def addItem(self, item: QLayoutItem) -> None:
|
||||||
self.itemList.append(item)
|
self.itemList.append(item)
|
||||||
@@ -104,12 +130,10 @@ 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(), margins.top() + margins.bottom())
|
size += QSize(margins.left() + margins.right(),
|
||||||
|
margins.top() + margins.bottom())
|
||||||
return size
|
return size
|
||||||
|
|
||||||
def doLayout(self, rect, testOnly):
|
def doLayout(self, rect, testOnly):
|
||||||
@@ -157,7 +181,7 @@ class ClickableLabel(QLabel):
|
|||||||
self._icon_size = icon_size
|
self._icon_size = icon_size
|
||||||
self._icon_space = icon_space
|
self._icon_space = icon_space
|
||||||
self._font_scale_factor = font_scale_factor
|
self._font_scale_factor = font_scale_factor
|
||||||
self._card_width = 250 # Значение по умолчанию
|
self._card_width = 250
|
||||||
if change_cursor:
|
if change_cursor:
|
||||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||||
self.updateFontSize()
|
self.updateFontSize()
|
||||||
@@ -175,28 +199,23 @@ class ClickableLabel(QLabel):
|
|||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def setCardWidth(self, card_width: int):
|
def setCardWidth(self, card_width: int):
|
||||||
"""Обновляет ширину карточки и пересчитывает размер шрифта."""
|
|
||||||
self._card_width = card_width
|
self._card_width = card_width
|
||||||
self.updateFontSize()
|
self.updateFontSize()
|
||||||
|
|
||||||
def updateFontSize(self):
|
def updateFontSize(self):
|
||||||
"""Обновляет размер шрифта на основе card_width и font_scale_factor."""
|
|
||||||
font = self.font()
|
font = self.font()
|
||||||
font_size = int(self._card_width * self._font_scale_factor)
|
font_size = int(self._card_width * self._font_scale_factor)
|
||||||
font.setPointSize(max(8, font_size)) # Минимальный размер шрифта 8
|
font.setPointSize(max(8, font_size))
|
||||||
self.setFont(font)
|
self.setFont(font)
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def paintEvent(self, event):
|
def paintEvent(self, event):
|
||||||
painter = QPainter(self)
|
painter = QPainter(self)
|
||||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||||
|
|
||||||
rect = self.contentsRect()
|
rect = self.contentsRect()
|
||||||
alignment = self.alignment()
|
alignment = self.alignment()
|
||||||
|
|
||||||
icon_size = self._icon_size
|
icon_size = self._icon_size
|
||||||
spacing = self._icon_space
|
spacing = self._icon_space
|
||||||
|
|
||||||
text = self.text()
|
text = self.text()
|
||||||
|
|
||||||
if self._icon:
|
if self._icon:
|
||||||
@@ -205,17 +224,11 @@ class ClickableLabel(QLabel):
|
|||||||
pixmap = None
|
pixmap = None
|
||||||
|
|
||||||
fm = QFontMetrics(self.font())
|
fm = QFontMetrics(self.font())
|
||||||
|
|
||||||
# Считаем, сколько места остаётся под текст
|
|
||||||
available_width = rect.width()
|
available_width = rect.width()
|
||||||
if pixmap:
|
if pixmap:
|
||||||
available_width -= (icon_size + spacing)
|
available_width -= (icon_size + spacing)
|
||||||
# Отступы по 2px с каждой стороны
|
|
||||||
available_width = max(0, available_width - 4)
|
available_width = max(0, available_width - 4)
|
||||||
|
|
||||||
# Получаем «обрезанный» текст с многоточием
|
|
||||||
display_text = fm.elidedText(text, Qt.TextElideMode.ElideRight, available_width)
|
display_text = fm.elidedText(text, Qt.TextElideMode.ElideRight, available_width)
|
||||||
|
|
||||||
text_width = fm.horizontalAdvance(display_text)
|
text_width = fm.horizontalAdvance(display_text)
|
||||||
text_height = fm.height()
|
text_height = fm.height()
|
||||||
total_width = text_width + (icon_size + spacing if pixmap else 0)
|
total_width = text_width + (icon_size + spacing if pixmap else 0)
|
||||||
@@ -285,8 +298,6 @@ class AutoSizeButton(QPushButton):
|
|||||||
|
|
||||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||||
self.setFlat(True)
|
self.setFlat(True)
|
||||||
|
|
||||||
# Изначально выставляем минимальную ширину
|
|
||||||
self.setMinimumWidth(50)
|
self.setMinimumWidth(50)
|
||||||
self.adjustFontSize()
|
self.adjustFontSize()
|
||||||
|
|
||||||
@@ -317,7 +328,6 @@ class AutoSizeButton(QPushButton):
|
|||||||
if not self._update_size:
|
if not self._update_size:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Определяем доступную ширину внутри кнопки
|
|
||||||
available_width = self.width()
|
available_width = self.width()
|
||||||
if self._icon:
|
if self._icon:
|
||||||
available_width -= self._icon_size
|
available_width -= self._icon_size
|
||||||
@@ -328,7 +338,6 @@ class AutoSizeButton(QPushButton):
|
|||||||
font = QFont(self._original_font)
|
font = QFont(self._original_font)
|
||||||
text = self._original_text
|
text = self._original_text
|
||||||
|
|
||||||
# Подбираем максимально возможный размер шрифта, при котором текст укладывается
|
|
||||||
chosen_size = self._max_font_size
|
chosen_size = self._max_font_size
|
||||||
for font_size in range(self._max_font_size, self._min_font_size - 1, -1):
|
for font_size in range(self._max_font_size, self._min_font_size - 1, -1):
|
||||||
font.setPointSize(font_size)
|
font.setPointSize(font_size)
|
||||||
@@ -341,14 +350,12 @@ class AutoSizeButton(QPushButton):
|
|||||||
font.setPointSize(chosen_size)
|
font.setPointSize(chosen_size)
|
||||||
self.setFont(font)
|
self.setFont(font)
|
||||||
|
|
||||||
# После выбора шрифта вычисляем требуемую ширину для полного отображения текста
|
|
||||||
fm = QFontMetrics(font)
|
fm = QFontMetrics(font)
|
||||||
text_width = fm.horizontalAdvance(text)
|
text_width = fm.horizontalAdvance(text)
|
||||||
required_width = text_width + margins.left() + margins.right() + self._padding * 2
|
required_width = text_width + margins.left() + margins.right() + self._padding * 2
|
||||||
if self._icon:
|
if self._icon:
|
||||||
required_width += self._icon_size
|
required_width += self._icon_size
|
||||||
|
|
||||||
# Если текущая ширина меньше требуемой, обновляем минимальную ширину
|
|
||||||
if self.width() < required_width:
|
if self.width() < required_width:
|
||||||
self.setMinimumWidth(required_width)
|
self.setMinimumWidth(required_width)
|
||||||
|
|
||||||
@@ -358,7 +365,6 @@ class AutoSizeButton(QPushButton):
|
|||||||
if not self._update_size:
|
if not self._update_size:
|
||||||
return super().sizeHint()
|
return super().sizeHint()
|
||||||
else:
|
else:
|
||||||
# Вычисляем оптимальный размер кнопки на основе текста и отступов
|
|
||||||
font = self.font()
|
font = self.font()
|
||||||
fm = QFontMetrics(font)
|
fm = QFontMetrics(font)
|
||||||
text_width = fm.horizontalAdvance(self._original_text)
|
text_width = fm.horizontalAdvance(self._original_text)
|
||||||
@@ -369,7 +375,6 @@ class AutoSizeButton(QPushButton):
|
|||||||
height = fm.height() + margins.top() + margins.bottom() + self._padding
|
height = fm.height() + margins.top() + margins.bottom() + self._padding
|
||||||
return QSize(width, height)
|
return QSize(width, height)
|
||||||
|
|
||||||
|
|
||||||
class NavLabel(QLabel):
|
class NavLabel(QLabel):
|
||||||
clicked = Signal()
|
clicked = Signal()
|
||||||
|
|
||||||
@@ -381,7 +386,6 @@ class NavLabel(QLabel):
|
|||||||
self._isChecked = False
|
self._isChecked = False
|
||||||
self.setProperty("checked", self._isChecked)
|
self.setProperty("checked", self._isChecked)
|
||||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||||
# Explicitly enable focus
|
|
||||||
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
||||||
|
|
||||||
def setCheckable(self, checkable):
|
def setCheckable(self, checkable):
|
||||||
@@ -400,7 +404,6 @@ class NavLabel(QLabel):
|
|||||||
|
|
||||||
def mousePressEvent(self, event):
|
def mousePressEvent(self, event):
|
||||||
if event.button() == Qt.MouseButton.LeftButton:
|
if event.button() == Qt.MouseButton.LeftButton:
|
||||||
# Ensure widget can take focus on click
|
|
||||||
self.setFocus(Qt.FocusReason.MouseFocusReason)
|
self.setFocus(Qt.FocusReason.MouseFocusReason)
|
||||||
if self._checkable:
|
if self._checkable:
|
||||||
self.setChecked(not self._isChecked)
|
self.setChecked(not self._isChecked)
|
||||||
|
Reference in New Issue
Block a user