diff --git a/Modules/Launcher/CarouselView.qml b/Modules/Launcher/CarouselView.qml index c28d0a9..f6a3c04 100755 --- a/Modules/Launcher/CarouselView.qml +++ b/Modules/Launcher/CarouselView.qml @@ -233,7 +233,19 @@ Item { } implicitHeight: tileHeight + Tokens.spacing.small / 2 + Tokens.padding.large + Tokens.padding.normal - implicitWidth: screen ? Math.max(0, screen.width - Config.bar.rounding * 50 - panels.bar.implicitWidth * 2) : 0 + implicitWidth: { + if (!screen) + return 0; + + const barMargins = Config.bar.border * 2; + let margins = 0; + if (visibilities.utilities || visibilities.sidebar) + margins = panels.utilities.implicitWidth; + const maxWidth = screen.width - (barMargins + margins) * 2 - Config.bar.rounding * 4; + + return maxWidth; + } + width: implicitWidth Component.onCompleted: { const idx = Wallpapers.list.findIndex(w => w.path === Wallpapers.actualCurrent); @@ -248,7 +260,7 @@ Item { anchors.fill: parent boundsBehavior: Flickable.StopAtBounds - cacheBuffer: root.width * 10 + cacheBuffer: root.width clip: false currentIndex: root.centerBlockStart displayMarginBeginning: root.arrangement.effectiveExtraWidth diff --git a/Modules/Launcher/Items/WallpaperTile.qml b/Modules/Launcher/Items/WallpaperTile.qml index 1c640a8..4923ef9 100755 --- a/Modules/Launcher/Items/WallpaperTile.qml +++ b/Modules/Launcher/Items/WallpaperTile.qml @@ -55,6 +55,8 @@ Item { height: root.tileHeight path: root.modelData ? root.modelData.path : "" smooth: root.isCurrent + sourceSize.height: root.tileHeight + sourceSize.width: root.tileHeight * (16 / 9) width: root.tileHeight * (16 / 9) } diff --git a/Plugins/ZShell/Internal/cachingimagemanager.cpp b/Plugins/ZShell/Internal/cachingimagemanager.cpp index 58e3112..7c58c72 100644 --- a/Plugins/ZShell/Internal/cachingimagemanager.cpp +++ b/Plugins/ZShell/Internal/cachingimagemanager.cpp @@ -1,5 +1,7 @@ #include "cachingimagemanager.hpp" +#include + #include #include #include @@ -7,10 +9,27 @@ #include #include #include +#include +#include #include namespace ZShell::internal { +namespace { + +QThreadPool* imageIoPool() { + static QThreadPool pool; + static const bool init = [] { + pool.setMaxThreadCount(std::max(2, QThread::idealThreadCount() / 2)); + return true; + }(); + (void)init; + + return &pool; +} + +} // namespace + qreal CachingImageManager::effectiveScale() const { if (m_item && m_item->window()) { return m_item->window()->devicePixelRatio(); @@ -101,14 +120,13 @@ void CachingImageManager::updateSource() { void CachingImageManager::updateSource(const QString& path) { if (path.isEmpty() || path == m_shaPath) { - // Path is empty or already calculating sha for path return; } m_shaPath = path; const auto future = - QtConcurrent::run(&CachingImageManager::sha256sum, path); + QtConcurrent::run(imageIoPool(), &CachingImageManager::sha256sum, path); const auto watcher = new QFutureWatcher(this); @@ -118,7 +136,16 @@ void CachingImageManager::updateSource(const QString& path) { this, [watcher, path, this]() { if (m_path != path) { - // Object is destroyed or path has changed, ignore + watcher->deleteLater(); + return; + } + + const QString key = watcher->result(); + + if (key.isEmpty()) { + if (m_shaPath == path) { + m_shaPath = QString(); + } watcher->deleteLater(); return; } @@ -126,19 +153,29 @@ void CachingImageManager::updateSource(const QString& path) { const QSize size = effectiveSize(); if (!m_item || !size.width() || !size.height()) { + if (m_shaPath == path) { + m_shaPath = QString(); + } watcher->deleteLater(); return; } const QString fillMode = m_item->property("fillMode").toString(); - // clang-format off - const QString filename = QString("%1@%2x%3-%4.png") - .arg(watcher->result()).arg(size.width()).arg(size.height()) - .arg(fillMode == "PreserveAspectCrop" ? "crop" : fillMode == "PreserveAspectFit" ? "fit" : "stretch"); - // clang-format on + const QString filename = + QString("%1@%2x%3-%4.png") + .arg(key) + .arg(size.width()) + .arg(size.height()) + .arg( + fillMode == "PreserveAspectCrop" ? "crop" + : fillMode == "PreserveAspectFit" ? "fit" + : "stretch"); const QUrl cache = m_cacheDir.resolved(QUrl(filename)); if (m_cachePath == cache) { + if (m_shaPath == path) { + m_shaPath = QString(); + } watcher->deleteLater(); return; } @@ -149,6 +186,9 @@ void CachingImageManager::updateSource(const QString& path) { if (!cache.isLocalFile()) { qWarning() << "CachingImageManager::updateSource: cachePath" << cache << "is not a local file"; + if (m_shaPath == path) { + m_shaPath = QString(); + } watcher->deleteLater(); return; } @@ -161,7 +201,6 @@ void CachingImageManager::updateSource(const QString& path) { createCache(path, cache.toLocalFile(), fillMode, size); } - // Clear current running sha if same if (m_shaPath == path) { m_shaPath = QString(); } @@ -170,7 +209,6 @@ void CachingImageManager::updateSource(const QString& path) { }); watcher->setFuture(future); - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) } QUrl CachingImageManager::cachePath() const { @@ -182,61 +220,105 @@ void CachingImageManager::createCache( const QString& cache, const QString& fillMode, const QSize& size) const { - QThreadPool::globalInstance()->start([path, cache, fillMode, size] { - QImage image(path); + auto* watcher = + new QFutureWatcher(const_cast(this)); - if (image.isNull()) { - qWarning() << "CachingImageManager::createCache: failed to read" - << path; - return; - } + connect( + watcher, + &QFutureWatcher::finished, + this, + [this, watcher, cache, path]() { + if (watcher->result() && m_item && m_path == path && + m_cachePath.toLocalFile() == cache) { + m_item->setProperty("source", QUrl::fromLocalFile(cache)); + } - image.convertTo(QImage::Format_ARGB32); + watcher->deleteLater(); + }); - if (fillMode == "PreserveAspectCrop") { - image = image.scaled( - size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); - } else if (fillMode == "PreserveAspectFit") { - image = image.scaled( - size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - } else { - image = image.scaled( - size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - } + const auto future = + QtConcurrent::run( + imageIoPool(), [path, cache, fillMode, size]() -> bool { + QImageReader reader(path); - if (fillMode == "PreserveAspectCrop" || - fillMode == "PreserveAspectFit") { - QImage canvas(size, QImage::Format_ARGB32); - canvas.fill(Qt::transparent); + if (reader.supportsOption(QImageIOHandler::ScaledSize)) { + const QSize full = reader.size(); + if (full.isValid() && !full.isEmpty()) { + const QSize hint = size * 2; + QSize scaled = full; + scaled.scale(hint, Qt::KeepAspectRatioByExpanding); + scaled = QSize( + std::min(scaled.width(), full.width()), + std::min(scaled.height(), full.height())); + reader.setScaledSize(scaled); + } + } - QPainter painter(&canvas); - painter.drawImage( - (size.width() - image.width()) / 2, - (size.height() - image.height()) / 2, - image); - painter.end(); + QImage image = reader.read(); - image = canvas; - } + if (image.isNull()) { + qWarning() + << "CachingImageManager::createCache: failed to read" + << path << reader.errorString(); + return false; + } - const QString parent = QFileInfo(cache).absolutePath(); - if (!QDir().mkpath(parent) || !image.save(cache)) { - qWarning() << "CachingImageManager::createCache: failed to save to" - << cache; - } - }); + image.convertTo(QImage::Format_ARGB32); + + if (fillMode == "PreserveAspectCrop") { + image = image.scaled( + size, + Qt::KeepAspectRatioByExpanding, + Qt::SmoothTransformation); + } else if (fillMode == "PreserveAspectFit") { + image = image.scaled( + size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + } else { + image = image.scaled( + size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } + + if (fillMode == "PreserveAspectCrop" || + fillMode == "PreserveAspectFit") { + QImage canvas(size, QImage::Format_ARGB32); + canvas.fill(Qt::transparent); + + QPainter painter(&canvas); + painter.drawImage( + (size.width() - image.width()) / 2, + (size.height() - image.height()) / 2, + image); + painter.end(); + + image = canvas; + } + + const QString parent = QFileInfo(cache).absolutePath(); + if (!QDir().mkpath(parent) || !image.save(cache)) { + qWarning() + << "CachingImageManager::createCache: failed to save to" + << cache; + return false; + } + + return true; + }); + + watcher->setFuture(future); } QString CachingImageManager::sha256sum(const QString& path) { - QFile file(path); - if (!file.open(QIODevice::ReadOnly)) { - qWarning() << "CachingImageManager::sha256sum: failed to open" << path; - return ""; + const QFileInfo info(path); + + if (!info.exists() || !info.isFile()) { + qWarning() << "CachingImageManager::sha256sum: failed to stat" << path; + return QString(); } QCryptographicHash hash(QCryptographicHash::Sha256); - hash.addData(&file); - file.close(); + hash.addData(path.toUtf8()); + hash.addData(QByteArray::number(info.size())); + hash.addData(QByteArray::number(info.lastModified().toMSecsSinceEpoch())); return hash.result().toHex(); }