From c2aa63b99abb6d1388f0e4f5e64a20ee24dd7551 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Sat, 13 Sep 2025 15:54:01 +1000 Subject: plugin: managers -> internal --- plugin/src/Caelestia/CMakeLists.txt | 2 +- plugin/src/Caelestia/Internal/CMakeLists.txt | 10 + .../src/Caelestia/Internal/cachingimagemanager.cpp | 223 +++++++++++++++++++++ .../src/Caelestia/Internal/cachingimagemanager.hpp | 65 ++++++ .../Internal/circularindicatormanager.cpp | 211 +++++++++++++++++++ .../Internal/circularindicatormanager.hpp | 72 +++++++ plugin/src/Caelestia/Managers/CMakeLists.txt | 10 - .../src/Caelestia/Managers/cachingimagemanager.cpp | 223 --------------------- .../src/Caelestia/Managers/cachingimagemanager.hpp | 65 ------ .../Managers/circularindicatormanager.cpp | 211 ------------------- .../Managers/circularindicatormanager.hpp | 72 ------- 11 files changed, 582 insertions(+), 582 deletions(-) create mode 100644 plugin/src/Caelestia/Internal/CMakeLists.txt create mode 100644 plugin/src/Caelestia/Internal/cachingimagemanager.cpp create mode 100644 plugin/src/Caelestia/Internal/cachingimagemanager.hpp create mode 100644 plugin/src/Caelestia/Internal/circularindicatormanager.cpp create mode 100644 plugin/src/Caelestia/Internal/circularindicatormanager.hpp delete mode 100644 plugin/src/Caelestia/Managers/CMakeLists.txt delete mode 100644 plugin/src/Caelestia/Managers/cachingimagemanager.cpp delete mode 100644 plugin/src/Caelestia/Managers/cachingimagemanager.hpp delete mode 100644 plugin/src/Caelestia/Managers/circularindicatormanager.cpp delete mode 100644 plugin/src/Caelestia/Managers/circularindicatormanager.hpp (limited to 'plugin/src') diff --git a/plugin/src/Caelestia/CMakeLists.txt b/plugin/src/Caelestia/CMakeLists.txt index 2832b5d..5fd8874 100644 --- a/plugin/src/Caelestia/CMakeLists.txt +++ b/plugin/src/Caelestia/CMakeLists.txt @@ -51,6 +51,6 @@ qml_module(caelestia PkgConfig::Qalculate ) -add_subdirectory(Managers) +add_subdirectory(Internal) add_subdirectory(Models) add_subdirectory(Services) diff --git a/plugin/src/Caelestia/Internal/CMakeLists.txt b/plugin/src/Caelestia/Internal/CMakeLists.txt new file mode 100644 index 0000000..62aa516 --- /dev/null +++ b/plugin/src/Caelestia/Internal/CMakeLists.txt @@ -0,0 +1,10 @@ +qml_module(caelestia-internal + URI Caelestia.Internal + SOURCES + cachingimagemanager.hpp cachingimagemanager.cpp + circularindicatormanager.hpp circularindicatormanager.cpp + LIBRARIES + Qt::Gui + Qt::Quick + Qt::Concurrent +) diff --git a/plugin/src/Caelestia/Internal/cachingimagemanager.cpp b/plugin/src/Caelestia/Internal/cachingimagemanager.cpp new file mode 100644 index 0000000..3394f89 --- /dev/null +++ b/plugin/src/Caelestia/Internal/cachingimagemanager.cpp @@ -0,0 +1,223 @@ +#include "cachingimagemanager.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace caelestia { + +qreal CachingImageManager::effectiveScale() const { + if (m_item && m_item->window()) { + return m_item->window()->devicePixelRatio(); + } + + return 1.0; +} + +QSize CachingImageManager::effectiveSize() const { + if (!m_item) { + return QSize(); + } + + const qreal scale = effectiveScale(); + const QSize size = QSizeF(m_item->width() * scale, m_item->height() * scale).toSize(); + m_item->setProperty("sourceSize", size); + return size; +} + +QQuickItem* CachingImageManager::item() const { + return m_item; +} + +void CachingImageManager::setItem(QQuickItem* item) { + if (m_item == item) { + return; + } + + if (m_widthConn) { + disconnect(m_widthConn); + } + if (m_heightConn) { + disconnect(m_heightConn); + } + + m_item = item; + emit itemChanged(); + + if (item) { + m_widthConn = connect(item, &QQuickItem::widthChanged, this, [this]() { + updateSource(); + }); + m_heightConn = connect(item, &QQuickItem::heightChanged, this, [this]() { + updateSource(); + }); + updateSource(); + } +} + +QUrl CachingImageManager::cacheDir() const { + return m_cacheDir; +} + +void CachingImageManager::setCacheDir(const QUrl& cacheDir) { + if (m_cacheDir == cacheDir) { + return; + } + + m_cacheDir = cacheDir; + if (!m_cacheDir.path().endsWith("/")) { + m_cacheDir.setPath(m_cacheDir.path() + "/"); + } + emit cacheDirChanged(); +} + +QString CachingImageManager::path() const { + return m_path; +} + +void CachingImageManager::setPath(const QString& path) { + if (m_path == path) { + return; + } + + m_path = path; + emit pathChanged(); + + if (!path.isEmpty()) { + updateSource(path); + } +} + +void CachingImageManager::updateSource() { + updateSource(m_path); +} + +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); + + const auto watcher = new QFutureWatcher(this); + + connect(watcher, &QFutureWatcher::finished, this, [watcher, path, this]() { + if (m_path != path) { + // Object is destroyed or path has changed, ignore + watcher->deleteLater(); + return; + } + + const QSize size = effectiveSize(); + + if (!m_item || !size.width() || !size.height()) { + 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 QUrl cache = m_cacheDir.resolved(QUrl(filename)); + if (m_cachePath == cache) { + watcher->deleteLater(); + return; + } + + m_cachePath = cache; + emit cachePathChanged(); + + if (!cache.isLocalFile()) { + qWarning() << "CachingImageManager::updateSource: cachePath" << cache << "is not a local file"; + watcher->deleteLater(); + return; + } + + const QImageReader reader(cache.toLocalFile()); + if (reader.canRead()) { + m_item->setProperty("source", cache); + } else { + m_item->setProperty("source", QUrl::fromLocalFile(path)); + createCache(path, cache.toLocalFile(), fillMode, size); + } + + // Clear current running sha if same + if (m_shaPath == path) { + m_shaPath = QString(); + } + + watcher->deleteLater(); + }); + + watcher->setFuture(future); +} + +QUrl CachingImageManager::cachePath() const { + return m_cachePath; +} + +void CachingImageManager::createCache( + const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const { + QThreadPool::globalInstance()->start([path, cache, fillMode, size] { + QImage image(path); + + if (image.isNull()) { + qWarning() << "CachingImageManager::createCache: failed to read" << path; + return; + } + + 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; + } + }); +} + +QString CachingImageManager::sha256sum(const QString& path) { + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << "CachingImageManager::sha256sum: failed to open" << path; + return ""; + } + + QCryptographicHash hash(QCryptographicHash::Sha256); + hash.addData(&file); + file.close(); + + return hash.result().toHex(); +} + +} // namespace caelestia diff --git a/plugin/src/Caelestia/Internal/cachingimagemanager.hpp b/plugin/src/Caelestia/Internal/cachingimagemanager.hpp new file mode 100644 index 0000000..f05ea34 --- /dev/null +++ b/plugin/src/Caelestia/Internal/cachingimagemanager.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include + +namespace caelestia { + +class CachingImageManager : public QObject { + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(QQuickItem* item READ item WRITE setItem NOTIFY itemChanged REQUIRED) + Q_PROPERTY(QUrl cacheDir READ cacheDir WRITE setCacheDir NOTIFY cacheDirChanged REQUIRED) + + Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QUrl cachePath READ cachePath NOTIFY cachePathChanged) + +public: + explicit CachingImageManager(QObject* parent = nullptr) + : QObject(parent) + , m_item(nullptr) {} + + [[nodiscard]] QQuickItem* item() const; + void setItem(QQuickItem* item); + + [[nodiscard]] QUrl cacheDir() const; + void setCacheDir(const QUrl& cacheDir); + + [[nodiscard]] QString path() const; + void setPath(const QString& path); + + [[nodiscard]] QUrl cachePath() const; + + Q_INVOKABLE void updateSource(); + Q_INVOKABLE void updateSource(const QString& path); + +signals: + void itemChanged(); + void cacheDirChanged(); + + void pathChanged(); + void cachePathChanged(); + void usingCacheChanged(); + +private: + QString m_shaPath; + + QQuickItem* m_item; + QUrl m_cacheDir; + + QString m_path; + QUrl m_cachePath; + + QMetaObject::Connection m_widthConn; + QMetaObject::Connection m_heightConn; + + [[nodiscard]] qreal effectiveScale() const; + [[nodiscard]] QSize effectiveSize() const; + + void createCache(const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const; + [[nodiscard]] static QString sha256sum(const QString& path); +}; + +} // namespace caelestia diff --git a/plugin/src/Caelestia/Internal/circularindicatormanager.cpp b/plugin/src/Caelestia/Internal/circularindicatormanager.cpp new file mode 100644 index 0000000..ac0c428 --- /dev/null +++ b/plugin/src/Caelestia/Internal/circularindicatormanager.cpp @@ -0,0 +1,211 @@ +#include "circularindicatormanager.hpp" +#include +#include + +namespace { + +namespace advance { + +constexpr qint32 TOTAL_CYCLES = 4; +constexpr qint32 TOTAL_DURATION_IN_MS = 5400; +constexpr qint32 DURATION_TO_EXPAND_IN_MS = 667; +constexpr qint32 DURATION_TO_COLLAPSE_IN_MS = 667; +constexpr qint32 DURATION_TO_COMPLETE_END_IN_MS = 333; +constexpr qint32 TAIL_DEGREES_OFFSET = -20; +constexpr qint32 EXTRA_DEGREES_PER_CYCLE = 250; +constexpr qint32 CONSTANT_ROTATION_DEGREES = 1520; + +constexpr std::array DELAY_TO_EXPAND_IN_MS = { 0, 1350, 2700, 4050 }; +constexpr std::array DELAY_TO_COLLAPSE_IN_MS = { 667, 2017, 3367, 4717 }; + +} // namespace advance + +namespace retreat { + +constexpr qint32 TOTAL_DURATION_IN_MS = 6000; +constexpr qint32 DURATION_SPIN_IN_MS = 500; +constexpr qint32 DURATION_GROW_ACTIVE_IN_MS = 3000; +constexpr qint32 DURATION_SHRINK_ACTIVE_IN_MS = 3000; +constexpr std::array DELAY_SPINS_IN_MS = { 0, 1500, 3000, 4500 }; +constexpr qint32 DELAY_GROW_ACTIVE_IN_MS = 0; +constexpr qint32 DELAY_SHRINK_ACTIVE_IN_MS = 3000; +constexpr qint32 DURATION_TO_COMPLETE_END_IN_MS = 500; + +// Constants for animation values. + +// The total degrees that a constant rotation goes by. +constexpr qint32 CONSTANT_ROTATION_DEGREES = 1080; +// Despite of the constant rotation, there are also 5 extra rotations the entire animation. The +// total degrees that each extra rotation goes by. +constexpr qint32 SPIN_ROTATION_DEGREES = 90; +constexpr std::array END_FRACTION_RANGE = { 0.10, 0.87 }; + +} // namespace retreat + +inline qreal getFractionInRange(qreal playtime, qreal start, qreal duration) { + const auto fraction = (playtime - start) / duration; + return std::clamp(fraction, 0.0, 1.0); +} + +} // namespace + +namespace caelestia { + +CircularIndicatorManager::CircularIndicatorManager(QObject* parent) + : QObject(parent) + , m_type(IndeterminateAnimationType::Advance) + , m_curve(QEasingCurve(QEasingCurve::BezierSpline)) + , m_progress(0) + , m_startFraction(0) + , m_endFraction(0) + , m_rotation(0) + , m_completeEndProgress(0) { + // Fast out slow in + m_curve.addCubicBezierSegment({ 0.4, 0.0 }, { 0.2, 1.0 }, { 1.0, 1.0 }); +} + +qreal CircularIndicatorManager::startFraction() const { + return m_startFraction; +} + +qreal CircularIndicatorManager::endFraction() const { + return m_endFraction; +} + +qreal CircularIndicatorManager::rotation() const { + return m_rotation; +} + +qreal CircularIndicatorManager::progress() const { + return m_progress; +} + +void CircularIndicatorManager::setProgress(qreal progress) { + update(progress); +} + +qreal CircularIndicatorManager::duration() const { + if (m_type == IndeterminateAnimationType::Advance) { + return advance::TOTAL_DURATION_IN_MS; + } else { + return retreat::TOTAL_DURATION_IN_MS; + } +} + +qreal CircularIndicatorManager::completeEndDuration() const { + if (m_type == IndeterminateAnimationType::Advance) { + return advance::DURATION_TO_COMPLETE_END_IN_MS; + } else { + return retreat::DURATION_TO_COMPLETE_END_IN_MS; + } +} + +CircularIndicatorManager::IndeterminateAnimationType CircularIndicatorManager::indeterminateAnimationType() const { + return m_type; +} + +void CircularIndicatorManager::setIndeterminateAnimationType(IndeterminateAnimationType t) { + if (m_type != t) { + m_type = t; + emit indeterminateAnimationTypeChanged(); + } +} + +qreal CircularIndicatorManager::completeEndProgress() const { + return m_completeEndProgress; +} + +void CircularIndicatorManager::setCompleteEndProgress(qreal progress) { + if (qFuzzyCompare(m_completeEndProgress + 1.0, progress + 1.0)) { + return; + } + + m_completeEndProgress = progress; + emit completeEndProgressChanged(); + + update(m_progress); +} + +void CircularIndicatorManager::update(qreal progress) { + if (qFuzzyCompare(m_progress + 1.0, progress + 1.0)) { + return; + } + + if (m_type == IndeterminateAnimationType::Advance) { + updateAdvance(progress); + } else { + updateRetreat(progress); + } + + m_progress = progress; + emit progressChanged(); +} + +void CircularIndicatorManager::updateRetreat(qreal progress) { + using namespace retreat; + const auto playtime = progress * TOTAL_DURATION_IN_MS; + + // Constant rotation. + const qreal constantRotation = CONSTANT_ROTATION_DEGREES * progress; + // Extra rotation for the faster spinning. + qreal spinRotation = 0; + for (const int spinDelay : DELAY_SPINS_IN_MS) { + spinRotation += m_curve.valueForProgress(getFractionInRange(playtime, spinDelay, DURATION_SPIN_IN_MS)) * + SPIN_ROTATION_DEGREES; + } + m_rotation = constantRotation + spinRotation; + emit rotationChanged(); + + // Grow active indicator. + qreal fraction = + m_curve.valueForProgress(getFractionInRange(playtime, DELAY_GROW_ACTIVE_IN_MS, DURATION_GROW_ACTIVE_IN_MS)); + fraction -= + m_curve.valueForProgress(getFractionInRange(playtime, DELAY_SHRINK_ACTIVE_IN_MS, DURATION_SHRINK_ACTIVE_IN_MS)); + + if (!qFuzzyIsNull(m_startFraction)) { + m_startFraction = 0.0; + emit startFractionChanged(); + } + const auto oldEndFrac = m_endFraction; + m_endFraction = std::lerp(END_FRACTION_RANGE[0], END_FRACTION_RANGE[1], fraction); + + // Completing animation. + if (m_completeEndProgress > 0) { + m_endFraction *= 1 - m_completeEndProgress; + } + + if (!qFuzzyCompare(m_endFraction + 1.0, oldEndFrac + 1.0)) { + emit endFractionChanged(); + } +} + +void CircularIndicatorManager::updateAdvance(qreal progress) { + using namespace advance; + const auto playtime = progress * TOTAL_DURATION_IN_MS; + + // Adds constant rotation to segment positions. + m_startFraction = CONSTANT_ROTATION_DEGREES * progress + TAIL_DEGREES_OFFSET; + m_endFraction = CONSTANT_ROTATION_DEGREES * progress; + + // Adds cycle specific rotation to segment positions. + for (size_t cycleIndex = 0; cycleIndex < TOTAL_CYCLES; ++cycleIndex) { + // While expanding. + qreal fraction = getFractionInRange(playtime, DELAY_TO_EXPAND_IN_MS[cycleIndex], DURATION_TO_EXPAND_IN_MS); + m_endFraction += m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE; + + // While collapsing. + fraction = getFractionInRange(playtime, DELAY_TO_COLLAPSE_IN_MS[cycleIndex], DURATION_TO_COLLAPSE_IN_MS); + m_startFraction += m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE; + } + + // Closes the gap between head and tail for complete end. + m_startFraction += (m_endFraction - m_startFraction) * m_completeEndProgress; + + m_startFraction /= 360.0; + m_endFraction /= 360.0; + + emit startFractionChanged(); + emit endFractionChanged(); +} + +} // namespace caelestia diff --git a/plugin/src/Caelestia/Internal/circularindicatormanager.hpp b/plugin/src/Caelestia/Internal/circularindicatormanager.hpp new file mode 100644 index 0000000..71da93d --- /dev/null +++ b/plugin/src/Caelestia/Internal/circularindicatormanager.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace caelestia { + +class CircularIndicatorManager : public QObject { + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(qreal startFraction READ startFraction NOTIFY startFractionChanged) + Q_PROPERTY(qreal endFraction READ endFraction NOTIFY endFractionChanged) + Q_PROPERTY(qreal rotation READ rotation NOTIFY rotationChanged) + Q_PROPERTY(qreal progress READ progress WRITE setProgress NOTIFY progressChanged) + Q_PROPERTY(qreal completeEndProgress READ completeEndProgress WRITE setCompleteEndProgress NOTIFY + completeEndProgressChanged) + Q_PROPERTY(qreal duration READ duration NOTIFY indeterminateAnimationTypeChanged) + Q_PROPERTY(qreal completeEndDuration READ completeEndDuration NOTIFY indeterminateAnimationTypeChanged) + Q_PROPERTY(IndeterminateAnimationType indeterminateAnimationType READ indeterminateAnimationType WRITE + setIndeterminateAnimationType NOTIFY indeterminateAnimationTypeChanged) + +public: + explicit CircularIndicatorManager(QObject* parent = nullptr); + + enum IndeterminateAnimationType { + Advance = 0, + Retreat + }; + Q_ENUM(IndeterminateAnimationType) + + [[nodiscard]] qreal startFraction() const; + [[nodiscard]] qreal endFraction() const; + [[nodiscard]] qreal rotation() const; + + [[nodiscard]] qreal progress() const; + void setProgress(qreal progress); + + [[nodiscard]] qreal completeEndProgress() const; + void setCompleteEndProgress(qreal progress); + + [[nodiscard]] qreal duration() const; + [[nodiscard]] qreal completeEndDuration() const; + + [[nodiscard]] IndeterminateAnimationType indeterminateAnimationType() const; + void setIndeterminateAnimationType(IndeterminateAnimationType t); + +signals: + void startFractionChanged(); + void endFractionChanged(); + void rotationChanged(); + void progressChanged(); + void completeEndProgressChanged(); + void indeterminateAnimationTypeChanged(); + +private: + IndeterminateAnimationType m_type; + QEasingCurve m_curve; + + qreal m_progress; + qreal m_startFraction; + qreal m_endFraction; + qreal m_rotation; + qreal m_completeEndProgress; + + void update(qreal progress); + void updateAdvance(qreal progress); + void updateRetreat(qreal progress); +}; + +} // namespace caelestia diff --git a/plugin/src/Caelestia/Managers/CMakeLists.txt b/plugin/src/Caelestia/Managers/CMakeLists.txt deleted file mode 100644 index d2083e3..0000000 --- a/plugin/src/Caelestia/Managers/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -qml_module(caelestia-managers - URI Caelestia.Managers - SOURCES - cachingimagemanager.hpp cachingimagemanager.cpp - circularindicatormanager.hpp circularindicatormanager.cpp - LIBRARIES - Qt::Gui - Qt::Quick - Qt::Concurrent -) diff --git a/plugin/src/Caelestia/Managers/cachingimagemanager.cpp b/plugin/src/Caelestia/Managers/cachingimagemanager.cpp deleted file mode 100644 index 3394f89..0000000 --- a/plugin/src/Caelestia/Managers/cachingimagemanager.cpp +++ /dev/null @@ -1,223 +0,0 @@ -#include "cachingimagemanager.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace caelestia { - -qreal CachingImageManager::effectiveScale() const { - if (m_item && m_item->window()) { - return m_item->window()->devicePixelRatio(); - } - - return 1.0; -} - -QSize CachingImageManager::effectiveSize() const { - if (!m_item) { - return QSize(); - } - - const qreal scale = effectiveScale(); - const QSize size = QSizeF(m_item->width() * scale, m_item->height() * scale).toSize(); - m_item->setProperty("sourceSize", size); - return size; -} - -QQuickItem* CachingImageManager::item() const { - return m_item; -} - -void CachingImageManager::setItem(QQuickItem* item) { - if (m_item == item) { - return; - } - - if (m_widthConn) { - disconnect(m_widthConn); - } - if (m_heightConn) { - disconnect(m_heightConn); - } - - m_item = item; - emit itemChanged(); - - if (item) { - m_widthConn = connect(item, &QQuickItem::widthChanged, this, [this]() { - updateSource(); - }); - m_heightConn = connect(item, &QQuickItem::heightChanged, this, [this]() { - updateSource(); - }); - updateSource(); - } -} - -QUrl CachingImageManager::cacheDir() const { - return m_cacheDir; -} - -void CachingImageManager::setCacheDir(const QUrl& cacheDir) { - if (m_cacheDir == cacheDir) { - return; - } - - m_cacheDir = cacheDir; - if (!m_cacheDir.path().endsWith("/")) { - m_cacheDir.setPath(m_cacheDir.path() + "/"); - } - emit cacheDirChanged(); -} - -QString CachingImageManager::path() const { - return m_path; -} - -void CachingImageManager::setPath(const QString& path) { - if (m_path == path) { - return; - } - - m_path = path; - emit pathChanged(); - - if (!path.isEmpty()) { - updateSource(path); - } -} - -void CachingImageManager::updateSource() { - updateSource(m_path); -} - -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); - - const auto watcher = new QFutureWatcher(this); - - connect(watcher, &QFutureWatcher::finished, this, [watcher, path, this]() { - if (m_path != path) { - // Object is destroyed or path has changed, ignore - watcher->deleteLater(); - return; - } - - const QSize size = effectiveSize(); - - if (!m_item || !size.width() || !size.height()) { - 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 QUrl cache = m_cacheDir.resolved(QUrl(filename)); - if (m_cachePath == cache) { - watcher->deleteLater(); - return; - } - - m_cachePath = cache; - emit cachePathChanged(); - - if (!cache.isLocalFile()) { - qWarning() << "CachingImageManager::updateSource: cachePath" << cache << "is not a local file"; - watcher->deleteLater(); - return; - } - - const QImageReader reader(cache.toLocalFile()); - if (reader.canRead()) { - m_item->setProperty("source", cache); - } else { - m_item->setProperty("source", QUrl::fromLocalFile(path)); - createCache(path, cache.toLocalFile(), fillMode, size); - } - - // Clear current running sha if same - if (m_shaPath == path) { - m_shaPath = QString(); - } - - watcher->deleteLater(); - }); - - watcher->setFuture(future); -} - -QUrl CachingImageManager::cachePath() const { - return m_cachePath; -} - -void CachingImageManager::createCache( - const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const { - QThreadPool::globalInstance()->start([path, cache, fillMode, size] { - QImage image(path); - - if (image.isNull()) { - qWarning() << "CachingImageManager::createCache: failed to read" << path; - return; - } - - 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; - } - }); -} - -QString CachingImageManager::sha256sum(const QString& path) { - QFile file(path); - if (!file.open(QIODevice::ReadOnly)) { - qWarning() << "CachingImageManager::sha256sum: failed to open" << path; - return ""; - } - - QCryptographicHash hash(QCryptographicHash::Sha256); - hash.addData(&file); - file.close(); - - return hash.result().toHex(); -} - -} // namespace caelestia diff --git a/plugin/src/Caelestia/Managers/cachingimagemanager.hpp b/plugin/src/Caelestia/Managers/cachingimagemanager.hpp deleted file mode 100644 index f05ea34..0000000 --- a/plugin/src/Caelestia/Managers/cachingimagemanager.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace caelestia { - -class CachingImageManager : public QObject { - Q_OBJECT - QML_ELEMENT - - Q_PROPERTY(QQuickItem* item READ item WRITE setItem NOTIFY itemChanged REQUIRED) - Q_PROPERTY(QUrl cacheDir READ cacheDir WRITE setCacheDir NOTIFY cacheDirChanged REQUIRED) - - Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) - Q_PROPERTY(QUrl cachePath READ cachePath NOTIFY cachePathChanged) - -public: - explicit CachingImageManager(QObject* parent = nullptr) - : QObject(parent) - , m_item(nullptr) {} - - [[nodiscard]] QQuickItem* item() const; - void setItem(QQuickItem* item); - - [[nodiscard]] QUrl cacheDir() const; - void setCacheDir(const QUrl& cacheDir); - - [[nodiscard]] QString path() const; - void setPath(const QString& path); - - [[nodiscard]] QUrl cachePath() const; - - Q_INVOKABLE void updateSource(); - Q_INVOKABLE void updateSource(const QString& path); - -signals: - void itemChanged(); - void cacheDirChanged(); - - void pathChanged(); - void cachePathChanged(); - void usingCacheChanged(); - -private: - QString m_shaPath; - - QQuickItem* m_item; - QUrl m_cacheDir; - - QString m_path; - QUrl m_cachePath; - - QMetaObject::Connection m_widthConn; - QMetaObject::Connection m_heightConn; - - [[nodiscard]] qreal effectiveScale() const; - [[nodiscard]] QSize effectiveSize() const; - - void createCache(const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const; - [[nodiscard]] static QString sha256sum(const QString& path); -}; - -} // namespace caelestia diff --git a/plugin/src/Caelestia/Managers/circularindicatormanager.cpp b/plugin/src/Caelestia/Managers/circularindicatormanager.cpp deleted file mode 100644 index ac0c428..0000000 --- a/plugin/src/Caelestia/Managers/circularindicatormanager.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include "circularindicatormanager.hpp" -#include -#include - -namespace { - -namespace advance { - -constexpr qint32 TOTAL_CYCLES = 4; -constexpr qint32 TOTAL_DURATION_IN_MS = 5400; -constexpr qint32 DURATION_TO_EXPAND_IN_MS = 667; -constexpr qint32 DURATION_TO_COLLAPSE_IN_MS = 667; -constexpr qint32 DURATION_TO_COMPLETE_END_IN_MS = 333; -constexpr qint32 TAIL_DEGREES_OFFSET = -20; -constexpr qint32 EXTRA_DEGREES_PER_CYCLE = 250; -constexpr qint32 CONSTANT_ROTATION_DEGREES = 1520; - -constexpr std::array DELAY_TO_EXPAND_IN_MS = { 0, 1350, 2700, 4050 }; -constexpr std::array DELAY_TO_COLLAPSE_IN_MS = { 667, 2017, 3367, 4717 }; - -} // namespace advance - -namespace retreat { - -constexpr qint32 TOTAL_DURATION_IN_MS = 6000; -constexpr qint32 DURATION_SPIN_IN_MS = 500; -constexpr qint32 DURATION_GROW_ACTIVE_IN_MS = 3000; -constexpr qint32 DURATION_SHRINK_ACTIVE_IN_MS = 3000; -constexpr std::array DELAY_SPINS_IN_MS = { 0, 1500, 3000, 4500 }; -constexpr qint32 DELAY_GROW_ACTIVE_IN_MS = 0; -constexpr qint32 DELAY_SHRINK_ACTIVE_IN_MS = 3000; -constexpr qint32 DURATION_TO_COMPLETE_END_IN_MS = 500; - -// Constants for animation values. - -// The total degrees that a constant rotation goes by. -constexpr qint32 CONSTANT_ROTATION_DEGREES = 1080; -// Despite of the constant rotation, there are also 5 extra rotations the entire animation. The -// total degrees that each extra rotation goes by. -constexpr qint32 SPIN_ROTATION_DEGREES = 90; -constexpr std::array END_FRACTION_RANGE = { 0.10, 0.87 }; - -} // namespace retreat - -inline qreal getFractionInRange(qreal playtime, qreal start, qreal duration) { - const auto fraction = (playtime - start) / duration; - return std::clamp(fraction, 0.0, 1.0); -} - -} // namespace - -namespace caelestia { - -CircularIndicatorManager::CircularIndicatorManager(QObject* parent) - : QObject(parent) - , m_type(IndeterminateAnimationType::Advance) - , m_curve(QEasingCurve(QEasingCurve::BezierSpline)) - , m_progress(0) - , m_startFraction(0) - , m_endFraction(0) - , m_rotation(0) - , m_completeEndProgress(0) { - // Fast out slow in - m_curve.addCubicBezierSegment({ 0.4, 0.0 }, { 0.2, 1.0 }, { 1.0, 1.0 }); -} - -qreal CircularIndicatorManager::startFraction() const { - return m_startFraction; -} - -qreal CircularIndicatorManager::endFraction() const { - return m_endFraction; -} - -qreal CircularIndicatorManager::rotation() const { - return m_rotation; -} - -qreal CircularIndicatorManager::progress() const { - return m_progress; -} - -void CircularIndicatorManager::setProgress(qreal progress) { - update(progress); -} - -qreal CircularIndicatorManager::duration() const { - if (m_type == IndeterminateAnimationType::Advance) { - return advance::TOTAL_DURATION_IN_MS; - } else { - return retreat::TOTAL_DURATION_IN_MS; - } -} - -qreal CircularIndicatorManager::completeEndDuration() const { - if (m_type == IndeterminateAnimationType::Advance) { - return advance::DURATION_TO_COMPLETE_END_IN_MS; - } else { - return retreat::DURATION_TO_COMPLETE_END_IN_MS; - } -} - -CircularIndicatorManager::IndeterminateAnimationType CircularIndicatorManager::indeterminateAnimationType() const { - return m_type; -} - -void CircularIndicatorManager::setIndeterminateAnimationType(IndeterminateAnimationType t) { - if (m_type != t) { - m_type = t; - emit indeterminateAnimationTypeChanged(); - } -} - -qreal CircularIndicatorManager::completeEndProgress() const { - return m_completeEndProgress; -} - -void CircularIndicatorManager::setCompleteEndProgress(qreal progress) { - if (qFuzzyCompare(m_completeEndProgress + 1.0, progress + 1.0)) { - return; - } - - m_completeEndProgress = progress; - emit completeEndProgressChanged(); - - update(m_progress); -} - -void CircularIndicatorManager::update(qreal progress) { - if (qFuzzyCompare(m_progress + 1.0, progress + 1.0)) { - return; - } - - if (m_type == IndeterminateAnimationType::Advance) { - updateAdvance(progress); - } else { - updateRetreat(progress); - } - - m_progress = progress; - emit progressChanged(); -} - -void CircularIndicatorManager::updateRetreat(qreal progress) { - using namespace retreat; - const auto playtime = progress * TOTAL_DURATION_IN_MS; - - // Constant rotation. - const qreal constantRotation = CONSTANT_ROTATION_DEGREES * progress; - // Extra rotation for the faster spinning. - qreal spinRotation = 0; - for (const int spinDelay : DELAY_SPINS_IN_MS) { - spinRotation += m_curve.valueForProgress(getFractionInRange(playtime, spinDelay, DURATION_SPIN_IN_MS)) * - SPIN_ROTATION_DEGREES; - } - m_rotation = constantRotation + spinRotation; - emit rotationChanged(); - - // Grow active indicator. - qreal fraction = - m_curve.valueForProgress(getFractionInRange(playtime, DELAY_GROW_ACTIVE_IN_MS, DURATION_GROW_ACTIVE_IN_MS)); - fraction -= - m_curve.valueForProgress(getFractionInRange(playtime, DELAY_SHRINK_ACTIVE_IN_MS, DURATION_SHRINK_ACTIVE_IN_MS)); - - if (!qFuzzyIsNull(m_startFraction)) { - m_startFraction = 0.0; - emit startFractionChanged(); - } - const auto oldEndFrac = m_endFraction; - m_endFraction = std::lerp(END_FRACTION_RANGE[0], END_FRACTION_RANGE[1], fraction); - - // Completing animation. - if (m_completeEndProgress > 0) { - m_endFraction *= 1 - m_completeEndProgress; - } - - if (!qFuzzyCompare(m_endFraction + 1.0, oldEndFrac + 1.0)) { - emit endFractionChanged(); - } -} - -void CircularIndicatorManager::updateAdvance(qreal progress) { - using namespace advance; - const auto playtime = progress * TOTAL_DURATION_IN_MS; - - // Adds constant rotation to segment positions. - m_startFraction = CONSTANT_ROTATION_DEGREES * progress + TAIL_DEGREES_OFFSET; - m_endFraction = CONSTANT_ROTATION_DEGREES * progress; - - // Adds cycle specific rotation to segment positions. - for (size_t cycleIndex = 0; cycleIndex < TOTAL_CYCLES; ++cycleIndex) { - // While expanding. - qreal fraction = getFractionInRange(playtime, DELAY_TO_EXPAND_IN_MS[cycleIndex], DURATION_TO_EXPAND_IN_MS); - m_endFraction += m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE; - - // While collapsing. - fraction = getFractionInRange(playtime, DELAY_TO_COLLAPSE_IN_MS[cycleIndex], DURATION_TO_COLLAPSE_IN_MS); - m_startFraction += m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE; - } - - // Closes the gap between head and tail for complete end. - m_startFraction += (m_endFraction - m_startFraction) * m_completeEndProgress; - - m_startFraction /= 360.0; - m_endFraction /= 360.0; - - emit startFractionChanged(); - emit endFractionChanged(); -} - -} // namespace caelestia diff --git a/plugin/src/Caelestia/Managers/circularindicatormanager.hpp b/plugin/src/Caelestia/Managers/circularindicatormanager.hpp deleted file mode 100644 index 71da93d..0000000 --- a/plugin/src/Caelestia/Managers/circularindicatormanager.hpp +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace caelestia { - -class CircularIndicatorManager : public QObject { - Q_OBJECT - QML_ELEMENT - - Q_PROPERTY(qreal startFraction READ startFraction NOTIFY startFractionChanged) - Q_PROPERTY(qreal endFraction READ endFraction NOTIFY endFractionChanged) - Q_PROPERTY(qreal rotation READ rotation NOTIFY rotationChanged) - Q_PROPERTY(qreal progress READ progress WRITE setProgress NOTIFY progressChanged) - Q_PROPERTY(qreal completeEndProgress READ completeEndProgress WRITE setCompleteEndProgress NOTIFY - completeEndProgressChanged) - Q_PROPERTY(qreal duration READ duration NOTIFY indeterminateAnimationTypeChanged) - Q_PROPERTY(qreal completeEndDuration READ completeEndDuration NOTIFY indeterminateAnimationTypeChanged) - Q_PROPERTY(IndeterminateAnimationType indeterminateAnimationType READ indeterminateAnimationType WRITE - setIndeterminateAnimationType NOTIFY indeterminateAnimationTypeChanged) - -public: - explicit CircularIndicatorManager(QObject* parent = nullptr); - - enum IndeterminateAnimationType { - Advance = 0, - Retreat - }; - Q_ENUM(IndeterminateAnimationType) - - [[nodiscard]] qreal startFraction() const; - [[nodiscard]] qreal endFraction() const; - [[nodiscard]] qreal rotation() const; - - [[nodiscard]] qreal progress() const; - void setProgress(qreal progress); - - [[nodiscard]] qreal completeEndProgress() const; - void setCompleteEndProgress(qreal progress); - - [[nodiscard]] qreal duration() const; - [[nodiscard]] qreal completeEndDuration() const; - - [[nodiscard]] IndeterminateAnimationType indeterminateAnimationType() const; - void setIndeterminateAnimationType(IndeterminateAnimationType t); - -signals: - void startFractionChanged(); - void endFractionChanged(); - void rotationChanged(); - void progressChanged(); - void completeEndProgressChanged(); - void indeterminateAnimationTypeChanged(); - -private: - IndeterminateAnimationType m_type; - QEasingCurve m_curve; - - qreal m_progress; - qreal m_startFraction; - qreal m_endFraction; - qreal m_rotation; - qreal m_completeEndProgress; - - void update(qreal progress); - void updateAdvance(qreal progress); - void updateRetreat(qreal progress); -}; - -} // namespace caelestia -- cgit v1.2.3-freya