Files
SoftwareManager/src/interface/dialogs/image.py
T
Vxrtrauter a29f58c023 Add fast layout update for image overlay
Introduce _apply_layout_fast to update the image overlay quickly on window resize using the cached QImage and FastTransformation. Called from the Resize event handler before the resize timer, it handles wallpaper mode (fast scale, center-crop to parent) and non-wallpaper mode (positioning by state.image_position and state.image_offset) to provide immediate visual feedback without running the heavier _apply_layout.
2026-04-08 01:52:08 +02:00

251 lines
9.9 KiB
Python

from PySide6.QtWidgets import QLabel, QGraphicsOpacityEffect, QStackedWidget
from PySide6.QtCore import Qt, QSize, QEvent, QObject, QTimer
from PySide6.QtGui import QImage, QPixmap
from utils.data.state import state
import darkdetect
import os
class Image(QObject):
def __init__(self, parent):
super().__init__(parent)
self.application = parent
self.overlay_label = QLabel(parent)
self.overlay_label.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True)
self.opacity_effect = QGraphicsOpacityEffect()
self.overlay_label.setGraphicsEffect(self.opacity_effect)
self._current_image_path = None
self._cached_qimage = None
self._wallpaper_active = False
self._original_stylesheets = {}
self._resize_timer = QTimer(self)
self._resize_timer.setSingleShot(True)
self._resize_timer.setInterval(100)
self._resize_timer.timeout.connect(self._on_resize_finished)
parent.installEventFilter(self)
state.image_changed.connect(self.update_image_overlay)
if state.image_path and os.path.exists(state.image_path):
self._load_and_display(state.image_path)
def eventFilter(self, obj, event):
if obj == self.application and event.type() == QEvent.Type.Resize:
if self._current_image_path:
self._apply_layout_fast()
self._resize_timer.start()
return False
def _on_resize_finished(self):
if self._current_image_path:
self._apply_layout()
def _set_wallpaper_transparency(self, enabled):
if self._wallpaper_active == enabled:
return
self._wallpaper_active = enabled
parent = self.application
central = parent.centralWidget()
if central:
central.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, enabled)
for attr in ['tab_wrapper', 'corner_widget', 'titlebar']:
w = getattr(parent, attr, None)
if w:
w.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, enabled)
if hasattr(parent, 'tabs'):
tabs = parent.tabs
tabs.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, enabled)
for i in range(tabs.count()):
page = tabs.widget(i)
if page:
page.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, enabled)
stack = tabs.findChild(QStackedWidget)
if stack:
stack.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, enabled)
tab_bar = tabs.tabBar()
if tab_bar:
tab_bar.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, enabled)
if 'tabs' not in self._original_stylesheets:
self._original_stylesheets['tabs'] = tabs.styleSheet()
if enabled:
tabs.setStyleSheet(
self._original_stylesheets['tabs']
+ "\nQTabWidget::pane { background: transparent; }"
+ "\nQTabBar { background: transparent; }"
+ "\nQTabBar::tab { background: transparent; }"
)
else:
tabs.setStyleSheet(self._original_stylesheets['tabs'])
# Searchbar
if hasattr(parent, 'searchbar'):
if 'searchbar' not in self._original_stylesheets:
self._original_stylesheets['searchbar'] = parent.searchbar.styleSheet()
if enabled:
parent.searchbar.setStyleSheet("QLineEdit { background-color: transparent; }")
else:
parent.searchbar.setStyleSheet(self._original_stylesheets['searchbar'])
# Download button
if hasattr(parent, 'dlbutton'):
if 'dlbutton' not in self._original_stylesheets:
self._original_stylesheets['dlbutton'] = parent.dlbutton.styleSheet()
if enabled:
parent.dlbutton.setStyleSheet("QPushButton { background-color: transparent; }")
else:
parent.dlbutton.setStyleSheet(self._original_stylesheets['dlbutton'])
# Labels
for attr in ['emptyResults', 'emptyDownload']:
w = getattr(parent, attr, None)
if w:
w.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, enabled)
if hasattr(parent, 'tracker_list'):
if 'tracker_list' not in self._original_stylesheets:
self._original_stylesheets['tracker_list'] = parent.tracker_list.styleSheet()
if enabled:
popup_bg = '#1e1e1e' if darkdetect.isDark() else '#ffffff'
parent.tracker_list.setStyleSheet(
"QComboBox { background-color: transparent; }"
f" QComboBox QAbstractItemView {{ background-color: {popup_bg}; }}"
)
else:
parent.tracker_list.setStyleSheet(self._original_stylesheets['tracker_list'])
def _load_and_display(self, image_path):
if state.image_enabled is not True:
self.overlay_label.hide()
self._set_wallpaper_transparency(False)
return
self._current_image_path = image_path
# Only reload from disk when the path actually changed
if self._cached_qimage is None or image_path != getattr(self, '_cached_path', None):
image = QImage(image_path)
if image.isNull():
self.overlay_label.hide()
self._cached_qimage = None
return
self._cached_qimage = image
self._cached_path = image_path
self._apply_layout()
def _apply_layout_fast(self):
if self._cached_qimage is None or not state.image_enabled:
return
parent = self.application
image = self._cached_qimage
if getattr(state, "image_as_wallpaper", False):
scaled = image.scaled(
parent.size(),
Qt.AspectRatioMode.KeepAspectRatioByExpanding,
Qt.TransformationMode.FastTransformation
)
cx = (scaled.width() - parent.width()) // 2
cy = (scaled.height() - parent.height()) // 2
cropped = scaled.copy(cx, cy, parent.width(), parent.height())
self.overlay_label.setPixmap(QPixmap.fromImage(cropped))
self.overlay_label.setGeometry(0, 0, parent.width(), parent.height())
else:
w = self.overlay_label.width()
h = self.overlay_label.height()
pos = state.image_position
off = int(state.image_offset)
if pos == "top-left":
x, y = off, off
elif pos == "top-right":
x, y = parent.width() - w - off, off
elif pos == "bottom-left":
x, y = off, parent.height() - h - off
elif pos == "center":
x, y = (parent.width() - w) // 2, (parent.height() - h) // 2
else:
x, y = parent.width() - w - off, parent.height() - h - off
self.overlay_label.move(x, y)
def _apply_layout(self):
if self._cached_qimage is None or not state.image_enabled:
self.overlay_label.hide()
return
parent = self.application
image = self._cached_qimage
if getattr(state, "image_as_wallpaper", False):
scaled_image = image.scaled(
parent.size(),
Qt.AspectRatioMode.KeepAspectRatioByExpanding,
Qt.TransformationMode.SmoothTransformation
)
crop_x = (scaled_image.width() - parent.width()) // 2
crop_y = (scaled_image.height() - parent.height()) // 2
scaled_image = scaled_image.copy(crop_x, crop_y, parent.width(), parent.height())
pixmap = QPixmap.fromImage(scaled_image)
self.overlay_label.setPixmap(pixmap)
self.overlay_label.setGeometry(0, 0, parent.width(), parent.height())
self.overlay_label.lower()
self.opacity_effect.setOpacity(state.image_opacity / 100)
self._set_wallpaper_transparency(True)
else:
self._set_wallpaper_transparency(False)
scaled_image = image.scaledToWidth(
state.image_width, Qt.TransformationMode.SmoothTransformation
)
max_width = parent.width()
max_height = parent.height()
if scaled_image.width() > max_width or scaled_image.height() > max_height:
scaled_image = scaled_image.scaled(
QSize(max_width, max_height),
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation,
)
pixmap = QPixmap.fromImage(scaled_image)
self.overlay_label.setPixmap(pixmap)
self.overlay_label.adjustSize()
self.overlay_label.raise_()
pos = state.image_position
off = int(state.image_offset)
w = self.overlay_label.width()
h = self.overlay_label.height()
if pos == "top-left":
x, y = off, off
elif pos == "top-right":
x, y = parent.width() - w - off, off
elif pos == "bottom-left":
x, y = off, parent.height() - h - off
elif pos == "center":
x, y = (parent.width() - w) // 2, (parent.height() - h) // 2
else: # bottom-right (default)
x, y = parent.width() - w - off, parent.height() - h - off
self.opacity_effect.setOpacity(state.image_opacity / 100)
self.overlay_label.move(x, y)
self.overlay_label.show()
def update_image_overlay(self, new_image_path):
if new_image_path != getattr(self, '_cached_path', None):
self._cached_qimage = None
self._load_and_display(new_image_path)