fixed applying crop being overwritten by multiple write calls, added wallpaper fade anim
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 14s
Python / test (pull_request) Successful in 31s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s

This commit is contained in:
2026-06-15 15:52:55 +02:00
parent 7e38f3f428
commit 2034f3a8db
5 changed files with 245 additions and 150 deletions
+107 -75
View File
@@ -7,6 +7,7 @@
#include <QtConcurrent>
#include <QSGImageNode>
#include <QQuickWindow>
#include <set>
namespace ZShell::internal {
@@ -21,10 +22,20 @@ WallpaperImage::~WallpaperImage() {
if (m_texture) delete m_texture;
}
void WallpaperImage::setStatus(const Status s) {
if (m_status == s)
return;
m_status = s;
emit statusChanged();
}
void WallpaperImage::setSource(const QUrl &source) {
if (m_source == source) return;
m_source = source;
emit sourceChanged();
setStatus(Loading);
loadImage();
}
@@ -57,28 +68,27 @@ void WallpaperImage::setCropY(qreal y) {
}
void WallpaperImage::setCropWidth(qreal w) {
if (w <= 0.0) w = 1.0;
if (qFuzzyCompare(m_cropWidth, w)) return;
m_cropWidth = w;
emit cropWidthChanged();
update();
if (w <= 0.0) w = 1.0;
if (qFuzzyCompare(m_cropWidth, w)) return;
m_cropWidth = w;
emit cropWidthChanged();
update();
}
void WallpaperImage::setCropHeight(qreal h) {
if (h <= 0.0) h = 1.0;
if (qFuzzyCompare(m_cropHeight, h)) return;
m_cropHeight = h;
emit cropHeightChanged();
update();
if (h <= 0.0) h = 1.0;
if (qFuzzyCompare(m_cropHeight, h)) return;
m_cropHeight = h;
emit cropHeightChanged();
update();
}
QString WallpaperImage::getCacheFilePath() const {
if (m_source.isEmpty() || m_screenResolution.isEmpty()) return QString();
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/zshell/imagecache";
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/zshell/imagecache";
QDir().mkpath(cachePath);
// Hash the source URL + resolution
QString id = m_source.toString() + "_" + QString::number(m_screenResolution.width()) + "x" + QString::number(m_screenResolution.height());
QByteArray hash = QCryptographicHash::hash(id.toUtf8(), QCryptographicHash::Md5).toHex();
@@ -86,52 +96,53 @@ QString WallpaperImage::getCacheFilePath() const {
}
void WallpaperImage::loadImage() {
if (m_source.isEmpty()) return;
if (m_source.isEmpty()) {
setStatus(Null);
return;
}
QString cacheFile = getCacheFilePath();
QString sourceFile = m_source.isLocalFile() ? m_source.toLocalFile() : m_source.toString();
// Qt resource path correction if passed as a standard URL string
if (sourceFile.startsWith("qrc:/")) {
sourceFile = sourceFile.mid(3); // Converts "qrc:/" to ":/"
}
QSize targetRes = m_screenResolution;
QString cacheFile = getCacheFilePath();
QString sourceFile = m_source.isLocalFile() ? m_source.toLocalFile() : m_source.toString();
// Run off the main thread to avoid blocking the UI
QFuture<QImage> future = QtConcurrent::run([sourceFile, cacheFile, targetRes]() -> QImage {
if (!targetRes.isEmpty() && !cacheFile.isEmpty() && QFileInfo::exists(cacheFile)) {
QImage cached(cacheFile);
if (!cached.isNull()) return cached;
}
if (sourceFile.startsWith("qrc:/")) {
sourceFile = sourceFile.mid(3);
}
QImage original(sourceFile);
if (original.isNull()) return QImage();
QSize targetRes = m_screenResolution;
if (targetRes.isEmpty()) {
// Screen resolution not set yet by QML, return the unscaled original for now to prevent a black screen
return original;
}
QFuture<QImage> future = QtConcurrent::run([sourceFile, cacheFile, targetRes]() -> QImage {
if (!targetRes.isEmpty() && !cacheFile.isEmpty() && QFileInfo::exists(cacheFile)) {
QImage cached(cacheFile);
if (!cached.isNull()) return cached;
}
// Check if original is strictly larger than screen resolution
if (original.width() > targetRes.width() || original.height() > targetRes.height()) {
QImage scaled = original.scaled(targetRes, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
if (!cacheFile.isEmpty()) scaled.save(cacheFile, "PNG");
return scaled;
}
QImage original(sourceFile);
if (original.isNull()) return QImage();
// Otherwise just cache and return the original
if (!cacheFile.isEmpty()) original.save(cacheFile, "PNG");
return original;
});
if (targetRes.isEmpty()) {
return original;
}
m_imageWatcher.setFuture(future);
if (original.width() > targetRes.width() || original.height() > targetRes.height()) {
QImage scaled = original.scaled(targetRes, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
if (!cacheFile.isEmpty()) scaled.save(cacheFile, "PNG");
return scaled;
}
if (!cacheFile.isEmpty()) original.save(cacheFile, "PNG");
return original;
});
m_imageWatcher.setFuture(future);
}
void WallpaperImage::handleImageLoaded() {
m_image = m_imageWatcher.result();
setStatus(m_image.isNull() ? Error : Ready);
m_textureDirty = true;
update(); // Request redraw
update();
}
QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) {
@@ -148,7 +159,7 @@ QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
if (m_textureDirty) {
if (m_texture) delete m_texture;
m_texture = window()->createTextureFromImage(m_image, QQuickWindow::TextureHasAlphaChannel);
m_texture = window()->createTextureFromImage(m_image, QQuickWindow::TextureHasAlphaChannel);
m_textureDirty = false;
}
@@ -157,40 +168,61 @@ QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
node->setRect(boundingRect());
node->setFiltering(QSGTexture::Linear);
qreal cW = m_cropWidth / m_zoom;
qreal cH = m_cropHeight / m_zoom;
qreal cW = m_cropWidth / m_zoom;
qreal cH = m_cropHeight / m_zoom;
QRectF reqRect(
m_cropX * m_texture->textureSize().width(),
m_cropY * m_texture->textureSize().height(),
cW * m_texture->textureSize().width(),
cH * m_texture->textureSize().height()
);
QRectF reqRect(
m_cropX * m_texture->textureSize().width(),
m_cropY * m_texture->textureSize().height(),
cW * m_texture->textureSize().width(),
cH * m_texture->textureSize().height()
);
QRectF bounds = boundingRect();
if (bounds.isEmpty() || reqRect.isEmpty()) return node;
QRectF bounds = boundingRect();
if (bounds.isEmpty() || reqRect.isEmpty()) return node;
qreal targetRatio = bounds.width() / bounds.height();
qreal reqRatio = reqRect.width() / reqRect.height();
qreal targetRatio = bounds.width() / bounds.height();
qreal reqRatio = reqRect.width() / reqRect.height();
QRectF sourceRect = reqRect;
QRectF sourceRect = reqRect;
// Force 'PreserveAspectCrop' behavior on the requested region
if (reqRatio > targetRatio) {
// Requested region is too wide, center-crop the sides
qreal newWidth = reqRect.height() * targetRatio;
qreal xOffset = (reqRect.width() - newWidth) / 2.0;
sourceRect.setX(reqRect.x() + xOffset);
sourceRect.setWidth(newWidth);
} else if (reqRatio < targetRatio) {
// Requested region is too tall, center-crop the top/bottom
qreal newHeight = reqRect.width() / targetRatio;
qreal yOffset = (reqRect.height() - newHeight) / 2.0;
sourceRect.setY(reqRect.y() + yOffset);
sourceRect.setHeight(newHeight);
}
if (reqRatio > targetRatio) {
qreal newWidth = reqRect.height() * targetRatio;
qreal xOffset = (reqRect.width() - newWidth) / 2.0;
sourceRect.setX(reqRect.x() + xOffset);
sourceRect.setWidth(newWidth);
} else if (reqRatio < targetRatio) {
qreal newHeight = reqRect.width() / targetRatio;
qreal yOffset = (reqRect.height() - newHeight) / 2.0;
sourceRect.setY(reqRect.y() + yOffset);
sourceRect.setHeight(newHeight);
}
node->setSourceRect(sourceRect);
QRectF normalizedActual(
sourceRect.x() / m_texture->textureSize().width(),
sourceRect.y() / m_texture->textureSize().height(),
sourceRect.width() / m_texture->textureSize().width(),
sourceRect.height() / m_texture->textureSize().height()
);
bool changed = false;
auto updateIfChanged = [&](qreal &dst, qreal value) {
if (!qFuzzyCompare(dst, value)) {
dst = value;
changed = true;
}
};
updateIfChanged(m_actualCropX, normalizedActual.x());
updateIfChanged(m_actualCropY, normalizedActual.y());
updateIfChanged(m_actualCropWidth, normalizedActual.width());
updateIfChanged(m_actualCropHeight, normalizedActual.height());
if (changed)
emit actualCropChanged();
node->setSourceRect(sourceRect);
}
return node;