🚧 wip: chekkupointo, basic functionality completed
This commit is contained in:
@@ -7,6 +7,8 @@ qt_add_qml_module(${CORELIB_NAME}
|
|||||||
Image/provider.hpp Image/provider.cpp
|
Image/provider.hpp Image/provider.cpp
|
||||||
Palette/data.hpp
|
Palette/data.hpp
|
||||||
Palette/manager.hpp Palette/manager.cpp
|
Palette/manager.hpp Palette/manager.cpp
|
||||||
|
Palette/domcolor.hpp Palette/domcolor.cpp
|
||||||
|
Palette/matchcolor.hpp Palette/matchcolor.cpp
|
||||||
Config/data.hpp
|
Config/data.hpp
|
||||||
Config/manager.hpp Config/manager.cpp
|
Config/manager.hpp Config/manager.cpp
|
||||||
logger.hpp logger.cpp
|
logger.hpp logger.cpp
|
||||||
|
|||||||
@@ -28,8 +28,8 @@
|
|||||||
// action.previewDebounceTime number 300 Debounce time for preview action in milliseconds
|
// action.previewDebounceTime number 300 Debounce time for preview action in milliseconds
|
||||||
// action.printSelected boolean true Whether to print the selected wallpaper path to stdout on confirm
|
// action.printSelected boolean true Whether to print the selected wallpaper path to stdout on confirm
|
||||||
// action.printPreview boolean false Whether to print the previewed wallpaper path to stdout on preview
|
// action.printPreview boolean false Whether to print the previewed wallpaper path to stdout on preview
|
||||||
// action.onSelected string "" Command to execute on confirmation ({{ path }} -> full path)
|
// action.onSelected string "" Command to execute on confirmation
|
||||||
// action.onPreview string "" Command to execute on preview ({{ path }} -> full path)
|
// action.onPreview string "" Command to execute on preview
|
||||||
// action.saveState array [] Useful for restore command
|
// action.saveState array [] Useful for restore command
|
||||||
// action.saveState[].key string "" Key of value to save, used as {{ key }} in onRestore command
|
// action.saveState[].key string "" Key of value to save, used as {{ key }} in onRestore command
|
||||||
// action.saveState[].default string "" Value to save, used when "cmd" is not set or command execution fails or output is empty
|
// action.saveState[].default string "" Value to save, used when "cmd" is not set or command execution fails or output is empty
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
// action.saveState[].timeout number 3000 Timeout for executing "cmd" in milliseconds. 0 or negative means no timeout
|
// action.saveState[].timeout number 3000 Timeout for executing "cmd" in milliseconds. 0 or negative means no timeout
|
||||||
// action.onRestore string "" Command to execute on restore ({{ key }} -> value defined or obtained in saveState)
|
// action.onRestore string "" Command to execute on restore ({{ key }} -> value defined or obtained in saveState)
|
||||||
// action.quitOnSelected boolean false Whether to quit the application after confirming a wallpaper
|
// action.quitOnSelected boolean false Whether to quit the application after confirming a wallpaper
|
||||||
|
// action.restoreOnCancel boolean true Whether to run the restore command after cancel without confirming a wallpaper
|
||||||
//
|
//
|
||||||
// style.image_width number 320 Width of each image
|
// style.image_width number 320 Width of each image
|
||||||
// style.image_height number 200 Height of each image
|
// style.image_height number 200 Height of each image
|
||||||
@@ -103,6 +104,7 @@ struct ActionConfigItems {
|
|||||||
bool printSelected = true;
|
bool printSelected = true;
|
||||||
bool printPreview = false;
|
bool printPreview = false;
|
||||||
bool quitOnSelected = false;
|
bool quitOnSelected = false;
|
||||||
|
bool restoreOnCancel = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StyleConfigItems {
|
struct StyleConfigItems {
|
||||||
|
|||||||
@@ -230,6 +230,12 @@ void WallReel::Core::Config::Manager::_loadActionConfig(const QJsonObject& root)
|
|||||||
m_actionConfig.quitOnSelected = val.toBool();
|
m_actionConfig.quitOnSelected = val.toBool();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (config.contains("restoreOnCancel")) {
|
||||||
|
const auto& val = config["restoreOnCancel"];
|
||||||
|
if (val.isBool()) {
|
||||||
|
m_actionConfig.restoreOnCancel = val.toBool();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WallReel::Core::Config::Manager::_loadStyleConfig(const QJsonObject& root) {
|
void WallReel::Core::Config::Manager::_loadStyleConfig(const QJsonObject& root) {
|
||||||
@@ -405,6 +411,7 @@ void WallReel::Core::Config::Manager::captureState() {
|
|||||||
process->disconnect();
|
process->disconnect();
|
||||||
|
|
||||||
QString result = success ? output : defaultVal;
|
QString result = success ? output : defaultVal;
|
||||||
|
Logger::debug(QString("Capture result for key '%1': %2 (success: %3)").arg(key).arg(result).arg(success));
|
||||||
if (result.isEmpty()) result = defaultVal;
|
if (result.isEmpty()) result = defaultVal;
|
||||||
|
|
||||||
_onCaptureResult(key, result);
|
_onCaptureResult(key, result);
|
||||||
@@ -444,7 +451,7 @@ void WallReel::Core::Config::Manager::captureState() {
|
|||||||
if (timer) {
|
if (timer) {
|
||||||
timer->start();
|
timer->start();
|
||||||
}
|
}
|
||||||
process->startCommand(item.cmd);
|
process->start("sh", QStringList() << "-c" << item.cmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
|
|
||||||
#include "../logger.hpp"
|
#include "Palette/domcolor.hpp"
|
||||||
|
#include "logger.hpp"
|
||||||
|
|
||||||
WallReel::Core::Image::Data* WallReel::Core::Image::Data::create(const QString& path, const QSize& size) {
|
WallReel::Core::Image::Data* WallReel::Core::Image::Data::create(const QString& path, const QSize& size) {
|
||||||
Data* ret = new Data(path, size);
|
Data* ret = new Data(path, size);
|
||||||
@@ -59,4 +60,7 @@ WallReel::Core::Image::Data::Data(const QString& path, const QSize& targetSize)
|
|||||||
|
|
||||||
// Create ID
|
// Create ID
|
||||||
m_id = QString::number(qHash(m_file.absoluteFilePath()));
|
m_id = QString::number(qHash(m_file.absoluteFilePath()));
|
||||||
|
|
||||||
|
// Get dominant color
|
||||||
|
m_dominantColor = Palette::getDominantColor(m_image);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ class Data {
|
|||||||
QString m_id;
|
QString m_id;
|
||||||
QFileInfo m_file;
|
QFileInfo m_file;
|
||||||
QImage m_image;
|
QImage m_image;
|
||||||
|
QColor m_dominantColor;
|
||||||
|
QHash<QString, QString> m_colorCache;
|
||||||
|
|
||||||
Data(const QString& path, const QSize& size);
|
Data(const QString& path, const QSize& size);
|
||||||
|
|
||||||
@@ -32,7 +34,18 @@ class Data {
|
|||||||
|
|
||||||
const QFileInfo& getFileInfo() const { return m_file; }
|
const QFileInfo& getFileInfo() const { return m_file; }
|
||||||
|
|
||||||
private:
|
const QColor& getDominantColor() const { return m_dominantColor; }
|
||||||
|
|
||||||
|
std::optional<QString> getCachedColor(const QString& paletteName) const {
|
||||||
|
if (m_colorCache.contains(paletteName)) {
|
||||||
|
return m_colorCache.value(paletteName);
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cacheColor(const QString& paletteName, const QString& colorName) {
|
||||||
|
m_colorCache.insert(paletteName, colorName);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace WallReel::Core::Image
|
} // namespace WallReel::Core::Image
|
||||||
|
|||||||
@@ -29,14 +29,13 @@ WallReel::Core::Image::Model::Model(
|
|||||||
emit progressChanged();
|
emit progressChanged();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update state when sort method changes
|
// Pipeline: sort -> filter -> update properties
|
||||||
connect(this, &Model::currentSortTypeChanged, this, &Model::_onSortMethodChanged);
|
connect(this, &Model::currentSortTypeChanged, this, &Model::_onSortMethodChanged);
|
||||||
connect(this, &Model::currentSortReverseChanged, this, &Model::_onSortMethodChanged);
|
connect(this, &Model::currentSortReverseChanged, this, &Model::_onSortMethodChanged);
|
||||||
|
connect(this, &Model::searchTextChanged, this, &Model::_onSearchTextChanged);
|
||||||
|
connect(this, &Model::focusedImageChanged, this, &Model::_updateFocusedProperties);
|
||||||
|
|
||||||
m_sortIndices.resize(4); // None, Name, Date, Size
|
m_sortIndices.resize(4); // None, Name, Date, Size
|
||||||
|
|
||||||
// Search text
|
|
||||||
connect(this, &Model::searchTextChanged, this, &Model::_onSearchTextChanged);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WallReel::Core::Image::Model::~Model() {
|
WallReel::Core::Image::Model::~Model() {
|
||||||
@@ -58,7 +57,7 @@ QVariant WallReel::Core::Image::Model::data(const QModelIndex& index, int role)
|
|||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
int actualIndex = fromProxyIndex(index.row());
|
int actualIndex = _convertProxyIndex(index.row());
|
||||||
if (actualIndex < 0 || actualIndex >= m_data.count()) {
|
if (actualIndex < 0 || actualIndex >= m_data.count()) {
|
||||||
Logger::debug("Actual index out of bounds: " + QString::number(actualIndex));
|
Logger::debug("Actual index out of bounds: " + QString::number(actualIndex));
|
||||||
return QVariant();
|
return QVariant();
|
||||||
@@ -126,12 +125,12 @@ void WallReel::Core::Image::Model::setSearchText(const QString& text) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const WallReel::Core::Image::Data* WallReel::Core::Image::Model::getDataPtrAt(int index) const {
|
WallReel::Core::Image::Data* WallReel::Core::Image::Model::imageAt(int index) {
|
||||||
if (index < 0 || index >= m_filteredIndices.count()) {
|
if (index < 0 || index >= m_filteredIndices.count()) {
|
||||||
Logger::debug("Invalid index requested: " + QString::number(index));
|
Logger::debug("Invalid index requested: " + QString::number(index));
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
int actualIndex = fromProxyIndex(index);
|
int actualIndex = _convertProxyIndex(index);
|
||||||
if (actualIndex < 0 || actualIndex >= m_data.count()) {
|
if (actualIndex < 0 || actualIndex >= m_data.count()) {
|
||||||
Logger::debug("Actual index out of bounds: " + QString::number(actualIndex));
|
Logger::debug("Actual index out of bounds: " + QString::number(actualIndex));
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -139,13 +138,17 @@ const WallReel::Core::Image::Data* WallReel::Core::Image::Model::getDataPtrAt(in
|
|||||||
return m_data[actualIndex];
|
return m_data[actualIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WallReel::Core::Image::Data* WallReel::Core::Image::Model::focusedImage() {
|
||||||
|
return imageAt(m_focusedIndex);
|
||||||
|
}
|
||||||
|
|
||||||
QVariant WallReel::Core::Image::Model::dataAt(int index, const QString& roleName) const {
|
QVariant WallReel::Core::Image::Model::dataAt(int index, const QString& roleName) const {
|
||||||
if (index < 0 || index >= m_filteredIndices.count()) {
|
if (index < 0 || index >= m_filteredIndices.count()) {
|
||||||
Logger::debug("Invalid index requested: " + QString::number(index));
|
Logger::debug("Invalid index requested: " + QString::number(index));
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
int actualIndex = fromProxyIndex(index);
|
int actualIndex = _convertProxyIndex(index);
|
||||||
if (actualIndex < 0 || actualIndex >= m_data.count()) {
|
if (actualIndex < 0 || actualIndex >= m_data.count()) {
|
||||||
Logger::debug("Actual index out of bounds: " + QString::number(actualIndex));
|
Logger::debug("Actual index out of bounds: " + QString::number(actualIndex));
|
||||||
return QVariant();
|
return QVariant();
|
||||||
@@ -185,26 +188,21 @@ void WallReel::Core::Image::Model::loadAndProcess(const QStringList& paths) {
|
|||||||
emit totalCountChanged();
|
emit totalCountChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
int WallReel::Core::Image::Model::fromProxyIndex(int proxyIndex) const {
|
|
||||||
if (proxyIndex < 0 || proxyIndex >= m_filteredIndices.size()) {
|
|
||||||
Logger::debug("Invalid proxy index requested: " + QString::number(proxyIndex));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return m_filteredIndices[m_currentSortReverse ? (m_filteredIndices.size() - 1 - proxyIndex) : proxyIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
void WallReel::Core::Image::Model::focusOnIndex(int index) {
|
void WallReel::Core::Image::Model::focusOnIndex(int index) {
|
||||||
if (index < 0 || index >= m_filteredIndices.count()) {
|
if (index < 0 || index >= m_filteredIndices.count()) {
|
||||||
Logger::debug("Invalid index to focus on: " + QString::number(index));
|
Logger::debug("Invalid index to focus on: " + QString::number(index));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int actualIndex = fromProxyIndex(index);
|
int actualIndex = _convertProxyIndex(index);
|
||||||
if (actualIndex < 0 || actualIndex >= m_data.count()) {
|
if (actualIndex < 0 || actualIndex >= m_data.count()) {
|
||||||
Logger::debug("Actual index out of bounds for focus: " + QString::number(actualIndex));
|
Logger::debug("Actual index out of bounds for focus: " + QString::number(actualIndex));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (m_focusedIndex != index) {
|
||||||
m_focusedIndex = index;
|
m_focusedIndex = index;
|
||||||
_updateFocusedName();
|
emit focusedImageChanged();
|
||||||
|
_updateFocusedProperties();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WallReel::Core::Image::Model::stop() {
|
void WallReel::Core::Image::Model::stop() {
|
||||||
@@ -216,6 +214,14 @@ void WallReel::Core::Image::Model::stop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int WallReel::Core::Image::Model::_convertProxyIndex(int proxyIndex) const {
|
||||||
|
if (proxyIndex < 0 || proxyIndex >= m_filteredIndices.size()) {
|
||||||
|
Logger::debug("Invalid proxy index requested: " + QString::number(proxyIndex));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return m_filteredIndices[proxyIndex];
|
||||||
|
}
|
||||||
|
|
||||||
void WallReel::Core::Image::Model::_clearData() {
|
void WallReel::Core::Image::Model::_clearData() {
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_provider.clear();
|
m_provider.clear();
|
||||||
@@ -257,11 +263,11 @@ void WallReel::Core::Image::Model::_updateSortIndices(Config::SortType type) {
|
|||||||
std::sort(indices.begin(), indices.end(), compareFunc);
|
std::sort(indices.begin(), indices.end(), compareFunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WallReel::Core::Image::Model::_updateFocusedName() {
|
void WallReel::Core::Image::Model::_updateFocusedProperties() {
|
||||||
if (m_focusedIndex < 0 || m_focusedIndex >= m_data.count()) {
|
if (m_focusedIndex < 0 || m_focusedIndex >= m_filteredIndices.size()) {
|
||||||
m_focusedName = "";
|
m_focusedName = "";
|
||||||
} else {
|
} else {
|
||||||
int actualIndex = fromProxyIndex(m_focusedIndex);
|
int actualIndex = _convertProxyIndex(m_focusedIndex);
|
||||||
if (actualIndex < 0 || actualIndex >= m_data.count()) {
|
if (actualIndex < 0 || actualIndex >= m_data.count()) {
|
||||||
m_focusedName = "";
|
m_focusedName = "";
|
||||||
} else {
|
} else {
|
||||||
@@ -272,19 +278,37 @@ void WallReel::Core::Image::Model::_updateFocusedName() {
|
|||||||
emit focusedNameChanged();
|
emit focusedNameChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WallReel::Core::Image::Model::_applySearchFilter() {
|
void WallReel::Core::Image::Model::_applySearchFilter(bool informView) {
|
||||||
emit layoutAboutToBeChanged();
|
|
||||||
m_filteredIndices.clear();
|
|
||||||
const auto& sortedIndices = m_sortIndices[static_cast<int>(m_currentSortType)];
|
const auto& sortedIndices = m_sortIndices[static_cast<int>(m_currentSortType)];
|
||||||
for (int i = 0; i < sortedIndices.size(); ++i) {
|
int srcPos = 0, resPos = 0;
|
||||||
int actualIndex = sortedIndices[i];
|
for (; srcPos < sortedIndices.size(); ++srcPos) {
|
||||||
|
int actualIndex = m_currentSortReverse ? sortedIndices[sortedIndices.size() - 1 - srcPos] : sortedIndices[srcPos];
|
||||||
|
const auto& item = m_data[actualIndex];
|
||||||
|
if (item->getFileName().contains(m_searchText, Qt::CaseInsensitive)) {
|
||||||
|
if (resPos >= m_filteredIndices.size() || m_filteredIndices[resPos] != actualIndex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
resPos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resPos == m_filteredIndices.size() && srcPos == sortedIndices.size()) {
|
||||||
|
return; // No change in filtered results
|
||||||
|
}
|
||||||
|
if (informView) {
|
||||||
|
emit layoutAboutToBeChanged();
|
||||||
|
}
|
||||||
|
m_filteredIndices.resize(resPos);
|
||||||
|
for (int i = srcPos; i < sortedIndices.size(); ++i) {
|
||||||
|
int actualIndex = m_currentSortReverse ? sortedIndices[sortedIndices.size() - 1 - i] : sortedIndices[i];
|
||||||
const auto& item = m_data[actualIndex];
|
const auto& item = m_data[actualIndex];
|
||||||
if (item->getFileName().contains(m_searchText, Qt::CaseInsensitive)) {
|
if (item->getFileName().contains(m_searchText, Qt::CaseInsensitive)) {
|
||||||
m_filteredIndices.append(actualIndex);
|
m_filteredIndices.append(actualIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (informView) {
|
||||||
emit layoutChanged();
|
emit layoutChanged();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void WallReel::Core::Image::Model::_onProgressValueChanged(int value) {
|
void WallReel::Core::Image::Model::_onProgressValueChanged(int value) {
|
||||||
Q_UNUSED(value);
|
Q_UNUSED(value);
|
||||||
@@ -311,7 +335,7 @@ void WallReel::Core::Image::Model::_onProcessingFinished() {
|
|||||||
_updateSortIndices(static_cast<Config::SortType>(i));
|
_updateSortIndices(static_cast<Config::SortType>(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
_applySearchFilter();
|
_applySearchFilter(false);
|
||||||
|
|
||||||
endResetModel();
|
endResetModel();
|
||||||
|
|
||||||
@@ -320,7 +344,6 @@ void WallReel::Core::Image::Model::_onProcessingFinished() {
|
|||||||
m_isLoading = false;
|
m_isLoading = false;
|
||||||
m_progressUpdateTimer.stop();
|
m_progressUpdateTimer.stop();
|
||||||
emit progressChanged();
|
emit progressChanged();
|
||||||
// emit isLoadingChanged();
|
|
||||||
// QTimer::singleShot(s_IsLoadingUpdateIntervalMs, this, [this]() {
|
// QTimer::singleShot(s_IsLoadingUpdateIntervalMs, this, [this]() {
|
||||||
// emit isLoadingChanged();
|
// emit isLoadingChanged();
|
||||||
// });
|
// });
|
||||||
@@ -329,8 +352,9 @@ void WallReel::Core::Image::Model::_onProcessingFinished() {
|
|||||||
|
|
||||||
void WallReel::Core::Image::Model::_onSortMethodChanged() {
|
void WallReel::Core::Image::Model::_onSortMethodChanged() {
|
||||||
_applySearchFilter();
|
_applySearchFilter();
|
||||||
|
emit focusedImageChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WallReel::Core::Image::Model::_onSearchTextChanged() {
|
void WallReel::Core::Image::Model::_onSearchTextChanged() {
|
||||||
_updateFocusedName();
|
emit focusedImageChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,23 +78,24 @@ class Model : public QAbstractListModel {
|
|||||||
|
|
||||||
Q_INVOKABLE void setSearchText(const QString& text);
|
Q_INVOKABLE void setSearchText(const QString& text);
|
||||||
|
|
||||||
const Data* getDataPtrAt(int index) const;
|
Data* imageAt(int index);
|
||||||
|
|
||||||
|
Data* focusedImage();
|
||||||
|
|
||||||
Q_INVOKABLE QVariant dataAt(int index, const QString& roleName) const;
|
Q_INVOKABLE QVariant dataAt(int index, const QString& roleName) const;
|
||||||
|
|
||||||
void loadAndProcess(const QStringList& paths);
|
void loadAndProcess(const QStringList& paths);
|
||||||
|
|
||||||
int fromProxyIndex(int proxyIndex) const;
|
|
||||||
|
|
||||||
Q_INVOKABLE void focusOnIndex(int index);
|
Q_INVOKABLE void focusOnIndex(int index);
|
||||||
|
|
||||||
Q_INVOKABLE void stop();
|
Q_INVOKABLE void stop();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
int _convertProxyIndex(int proxyIndex) const;
|
||||||
void _clearData();
|
void _clearData();
|
||||||
void _updateSortIndices(Config::SortType type);
|
void _updateSortIndices(Config::SortType type);
|
||||||
void _updateFocusedName();
|
void _updateFocusedProperties();
|
||||||
void _applySearchFilter();
|
void _applySearchFilter(bool informView = true);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void isLoadingChanged();
|
void isLoadingChanged();
|
||||||
@@ -104,6 +105,7 @@ class Model : public QAbstractListModel {
|
|||||||
void currentSortReverseChanged();
|
void currentSortReverseChanged();
|
||||||
void focusedNameChanged();
|
void focusedNameChanged();
|
||||||
void searchTextChanged();
|
void searchTextChanged();
|
||||||
|
void focusedImageChanged();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void _onProgressValueChanged(int value);
|
void _onProgressValueChanged(int value);
|
||||||
@@ -129,6 +131,9 @@ class Model : public QAbstractListModel {
|
|||||||
// QTimer m_searchDebounceTimer;
|
// QTimer m_searchDebounceTimer;
|
||||||
// static constexpr int s_SearchDebounceIntervalMs = 300;
|
// static constexpr int s_SearchDebounceIntervalMs = 300;
|
||||||
|
|
||||||
|
QColor m_focusedColor{};
|
||||||
|
QString m_focusedColorName{};
|
||||||
|
|
||||||
QFutureWatcher<Data*> m_watcher;
|
QFutureWatcher<Data*> m_watcher;
|
||||||
bool m_isLoading = false;
|
bool m_isLoading = false;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#ifndef WALLREEL_PALETTE_DATA_HPP
|
#ifndef WALLREEL_PALETTE_DATA_HPP
|
||||||
#define WALLREEL_PALETTE_DATA_HPP
|
#define WALLREEL_PALETTE_DATA_HPP
|
||||||
|
|
||||||
|
#include <qcolor.h>
|
||||||
|
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
@@ -37,6 +39,10 @@ struct PaletteItem {
|
|||||||
}
|
}
|
||||||
return QColor();
|
return QColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator==(const PaletteItem& other) const {
|
||||||
|
return name == other.name;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace WallReel::Core::Palette
|
} // namespace WallReel::Core::Palette
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
#include "manager.hpp"
|
#include "manager.hpp"
|
||||||
|
|
||||||
|
#include "Palette/matchcolor.hpp"
|
||||||
|
#include "Utils/misc.hpp"
|
||||||
|
#include "logger.hpp"
|
||||||
#include "predefined.hpp"
|
#include "predefined.hpp"
|
||||||
|
|
||||||
WallReel::Core::Palette::Manager::Manager(
|
WallReel::Core::Palette::Manager::Manager(
|
||||||
const Config::PaletteConfigItems& config,
|
const Config::PaletteConfigItems& config,
|
||||||
QObject* parent) : QObject(parent) {
|
Image::Model& imageModel,
|
||||||
|
QObject* parent) : QObject(parent), m_imageModel(imageModel) {
|
||||||
|
connect(&m_imageModel, &Image::Model::focusedImageChanged, this, &Manager::updateColor);
|
||||||
|
|
||||||
// The new ones overrides the old ones, use a hashtable to track
|
// The new ones overrides the old ones, use a hashtable to track
|
||||||
// the latest index of each palette name, then only insert the
|
// the latest index of each palette name, then only insert the
|
||||||
// ones whose index matches the latest index in the hashtable
|
// ones whose index matches the latest index in the hashtable
|
||||||
@@ -40,3 +46,59 @@ WallReel::Core::Palette::Manager::Manager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WallReel::Core::Palette::Manager::updateColor() {
|
||||||
|
bool hasResult = false;
|
||||||
|
Utils::Defer defer([&]() {
|
||||||
|
if (!hasResult) {
|
||||||
|
m_displayColor = QColor();
|
||||||
|
m_displayColorName = "";
|
||||||
|
}
|
||||||
|
emit colorChanged();
|
||||||
|
emit colorNameChanged();
|
||||||
|
});
|
||||||
|
auto imageData = m_imageModel.focusedImage();
|
||||||
|
if (!imageData || !imageData->isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// No palette selected, use dominant color
|
||||||
|
if (!m_selectedPalette.has_value()) {
|
||||||
|
m_displayColor = imageData->getDominantColor();
|
||||||
|
m_displayColorName = "";
|
||||||
|
hasResult = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Only palette selected, use the colosest color in the palette
|
||||||
|
if (!m_selectedColor.has_value()) {
|
||||||
|
auto cached = imageData->getCachedColor(m_selectedPalette->name);
|
||||||
|
if (cached.has_value()) {
|
||||||
|
auto it = std::find_if(m_selectedPalette->colors.begin(),
|
||||||
|
m_selectedPalette->colors.end(),
|
||||||
|
[&](const ColorItem& item) {
|
||||||
|
return item.name == cached.value();
|
||||||
|
});
|
||||||
|
if (it != m_selectedPalette->colors.end()) {
|
||||||
|
Logger::debug("Using cached color match for image " + imageData->getFileName() +
|
||||||
|
": " + it->name);
|
||||||
|
m_displayColor = it->color;
|
||||||
|
m_displayColorName = it->name;
|
||||||
|
hasResult = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto matched = bestMatch(
|
||||||
|
imageData->getDominantColor(),
|
||||||
|
m_selectedPalette.value().colors);
|
||||||
|
Logger::debug("Computed color match for image " + imageData->getFileName() + ": " +
|
||||||
|
matched.name);
|
||||||
|
imageData->cacheColor(m_selectedPalette->name, matched.name);
|
||||||
|
m_displayColor = matched.color;
|
||||||
|
m_displayColorName = matched.name;
|
||||||
|
hasResult = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Both are set, use them
|
||||||
|
m_displayColor = m_selectedColor.value().color;
|
||||||
|
m_displayColorName = m_selectedColor.value().name;
|
||||||
|
hasResult = true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
#ifndef WALLREEL_PALETTE_MANAGER_HPP
|
#ifndef WALLREEL_PALETTE_MANAGER_HPP
|
||||||
#define WALLREEL_PALETTE_MANAGER_HPP
|
#define WALLREEL_PALETTE_MANAGER_HPP
|
||||||
|
|
||||||
|
#include <qcolor.h>
|
||||||
|
|
||||||
#include "Config/data.hpp"
|
#include "Config/data.hpp"
|
||||||
|
#include "Image/model.hpp"
|
||||||
#include "data.hpp"
|
#include "data.hpp"
|
||||||
|
|
||||||
namespace WallReel::Core::Palette {
|
namespace WallReel::Core::Palette {
|
||||||
@@ -9,24 +12,74 @@ namespace WallReel::Core::Palette {
|
|||||||
class Manager : public QObject {
|
class Manager : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(QList<PaletteItem> availablePalettes READ availablePalettes CONSTANT)
|
Q_PROPERTY(QList<PaletteItem> availablePalettes READ availablePalettes CONSTANT)
|
||||||
|
Q_PROPERTY(QColor color READ color NOTIFY colorChanged)
|
||||||
|
Q_PROPERTY(QString colorName READ colorName NOTIFY colorNameChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Manager(const Config::PaletteConfigItems& config,
|
Manager(const Config::PaletteConfigItems& config,
|
||||||
|
Image::Model& imageModel,
|
||||||
QObject* parent = nullptr);
|
QObject* parent = nullptr);
|
||||||
|
|
||||||
const QList<PaletteItem>& availablePalettes() const {
|
const QList<PaletteItem>& availablePalettes() const {
|
||||||
return m_palettes;
|
return m_palettes;
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_INVOKABLE PaletteItem getPalette(const QString& name) const {
|
const QColor& color() const {
|
||||||
for (const auto& p : m_palettes) {
|
return m_displayColor;
|
||||||
if (p.name == name) return p;
|
|
||||||
}
|
|
||||||
return PaletteItem();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QString& colorName() const {
|
||||||
|
return m_displayColorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_INVOKABLE void setSelectedPalette(const QVariant& paletteVar) {
|
||||||
|
if (paletteVar.isNull() || !paletteVar.isValid()) {
|
||||||
|
m_selectedPalette = std::nullopt;
|
||||||
|
} else {
|
||||||
|
m_selectedPalette = paletteVar.value<PaletteItem>();
|
||||||
|
}
|
||||||
|
updateColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_INVOKABLE void setSelectedColor(const QVariant& colorVar) {
|
||||||
|
if (colorVar.isNull() || !colorVar.isValid()) {
|
||||||
|
m_selectedColor = std::nullopt;
|
||||||
|
} else {
|
||||||
|
m_selectedColor = colorVar.value<ColorItem>();
|
||||||
|
}
|
||||||
|
updateColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString getSelectedPaletteName() const {
|
||||||
|
return m_selectedPalette ? m_selectedPalette->name : QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString getCurrentColorName() const {
|
||||||
|
return m_displayColorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString getCurrentColorHex() const {
|
||||||
|
return m_displayColor.isValid()
|
||||||
|
? m_displayColor.name()
|
||||||
|
: QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void updateColor();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void colorChanged();
|
||||||
|
void colorNameChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Image::Model& m_imageModel;
|
||||||
|
|
||||||
QList<PaletteItem> m_palettes;
|
QList<PaletteItem> m_palettes;
|
||||||
|
std::optional<PaletteItem> m_selectedPalette = std::nullopt;
|
||||||
|
std::optional<ColorItem> m_selectedColor = std::nullopt;
|
||||||
|
|
||||||
|
QColor m_displayColor;
|
||||||
|
QString m_displayColorName;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace WallReel::Core::Palette
|
} // namespace WallReel::Core::Palette
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#include "matchcolor.hpp"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <cmath>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace WallReel::Core::Palette {
|
||||||
|
|
||||||
|
const ColorItem& bestMatch(const QColor& target, const QList<ColorItem>& candidates) {
|
||||||
|
if (candidates.isEmpty() || !target.isValid()) {
|
||||||
|
static ColorItem emptyItem;
|
||||||
|
return emptyItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
int target_r = target.red();
|
||||||
|
int target_g = target.green();
|
||||||
|
int target_b = target.blue();
|
||||||
|
|
||||||
|
int target_h = target.hsvHue();
|
||||||
|
double target_s = target.hsvSaturationF();
|
||||||
|
|
||||||
|
const ColorItem* closest_flavor = nullptr;
|
||||||
|
double min_distance = std::numeric_limits<double>::max();
|
||||||
|
|
||||||
|
for (const auto& candidate : candidates) {
|
||||||
|
QColor p_color = candidate.color;
|
||||||
|
int p_r = p_color.red();
|
||||||
|
int p_g = p_color.green();
|
||||||
|
int p_b = p_color.blue();
|
||||||
|
|
||||||
|
int p_h = p_color.hsvHue();
|
||||||
|
|
||||||
|
// RGB distance with weighting
|
||||||
|
double rmean = (target_r + p_r) / 2.0;
|
||||||
|
double dr = target_r - p_r;
|
||||||
|
double dg = target_g - p_g;
|
||||||
|
double db = target_b - p_b;
|
||||||
|
|
||||||
|
double rgb_distance = std::sqrt((2.0 + rmean / 256.0) * dr * dr + 4.0 * dg * dg +
|
||||||
|
(2.0 + (255.0 - rmean) / 256.0) * db * db);
|
||||||
|
|
||||||
|
// Hue difference (with wrapping)
|
||||||
|
double hue_diff = 0.0;
|
||||||
|
if (target_h != -1 && p_h != -1) {
|
||||||
|
hue_diff = std::abs(target_h - p_h);
|
||||||
|
if (hue_diff > 180.0) {
|
||||||
|
hue_diff = 360.0 - hue_diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase hue weight when saturation is high
|
||||||
|
double hue_weight = (target_s > 0.20) ? 2.0 : 0.5;
|
||||||
|
|
||||||
|
double total_distance = rgb_distance + (hue_diff * hue_weight * 3.0);
|
||||||
|
|
||||||
|
if (total_distance < min_distance) {
|
||||||
|
min_distance = total_distance;
|
||||||
|
closest_flavor = &candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closest_flavor) {
|
||||||
|
return *closest_flavor;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ColorItem emptyItem;
|
||||||
|
return emptyItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace WallReel::Core::Palette
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef WALLREEL_CORE_PALETTE_MATCHCOLOR_HPP
|
||||||
|
#define WALLREEL_CORE_PALETTE_MATCHCOLOR_HPP
|
||||||
|
|
||||||
|
#include "data.hpp"
|
||||||
|
|
||||||
|
namespace WallReel::Core::Palette {
|
||||||
|
|
||||||
|
const ColorItem& bestMatch(const QColor& target, const QList<ColorItem>& candidates);
|
||||||
|
|
||||||
|
} // namespace WallReel::Core::Palette
|
||||||
|
|
||||||
|
#endif // WALLREEL_CORE_PALETTE_MATCHCOLOR_HPP
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "Config/data.hpp"
|
#include "Config/data.hpp"
|
||||||
#include "Image/model.hpp"
|
#include "Image/model.hpp"
|
||||||
|
#include "Palette/manager.hpp"
|
||||||
#include "Service/wallpaper.hpp"
|
#include "Service/wallpaper.hpp"
|
||||||
#include "logger.hpp"
|
#include "logger.hpp"
|
||||||
|
|
||||||
@@ -19,10 +20,16 @@ class Manager : public QObject {
|
|||||||
public:
|
public:
|
||||||
Manager(
|
Manager(
|
||||||
const Config::ActionConfigItems& actionConfig,
|
const Config::ActionConfigItems& actionConfig,
|
||||||
const Image::Model& imageModel,
|
Image::Model& imageModel,
|
||||||
QObject* parent = nullptr) : m_imageModel(imageModel) {
|
Palette::Manager& paletteManager,
|
||||||
m_wallpaperService = new WallpaperService(actionConfig, this);
|
QObject* parent = nullptr) : m_actionConfig(actionConfig), m_imageModel(imageModel), m_paletteManager(paletteManager) {
|
||||||
|
m_wallpaperService = new WallpaperService(m_actionConfig, m_paletteManager, this);
|
||||||
|
|
||||||
|
// Listen on image change
|
||||||
|
connect(&m_imageModel, &Image::Model::focusedImageChanged, this, &Manager::previewWallpaper);
|
||||||
|
// Listen on palette change
|
||||||
|
connect(&m_paletteManager, &Palette::Manager::colorChanged, this, &Manager::previewWallpaper);
|
||||||
|
connect(&m_paletteManager, &Palette::Manager::colorNameChanged, this, &Manager::previewWallpaper);
|
||||||
// Forward signals
|
// Forward signals
|
||||||
// Direct signal 2 signal connection
|
// Direct signal 2 signal connection
|
||||||
connect(m_wallpaperService, &WallpaperService::previewCompleted, this, &Manager::previewCompleted);
|
connect(m_wallpaperService, &WallpaperService::previewCompleted, this, &Manager::previewCompleted);
|
||||||
@@ -38,7 +45,7 @@ class Manager : public QObject {
|
|||||||
}
|
}
|
||||||
m_isProcessing = true;
|
m_isProcessing = true;
|
||||||
emit isProcessingChanged();
|
emit isProcessingChanged();
|
||||||
const auto* data = m_imageModel.getDataPtrAt(index);
|
const auto* data = m_imageModel.imageAt(index);
|
||||||
if (data) {
|
if (data) {
|
||||||
m_wallpaperService->select(*data);
|
m_wallpaperService->select(*data);
|
||||||
} else {
|
} else {
|
||||||
@@ -48,15 +55,6 @@ class Manager : public QObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_INVOKABLE void previewWallpaper(int index) {
|
|
||||||
const auto* data = m_imageModel.getDataPtrAt(index);
|
|
||||||
if (data) {
|
|
||||||
m_wallpaperService->preview(*data);
|
|
||||||
} else {
|
|
||||||
emit previewCompleted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE void restore() {
|
Q_INVOKABLE void restore() {
|
||||||
if (m_isProcessing) {
|
if (m_isProcessing) {
|
||||||
Logger::debug("Already processing an action, ignoring restore request");
|
Logger::debug("Already processing an action, ignoring restore request");
|
||||||
@@ -67,8 +65,31 @@ class Manager : public QObject {
|
|||||||
m_wallpaperService->restore();
|
m_wallpaperService->restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Q_INVOKABLE void cancel() {
|
||||||
|
m_wallpaperService->stopAll();
|
||||||
|
if (m_actionConfig.restoreOnCancel) {
|
||||||
|
connect(m_wallpaperService, &WallpaperService::restoreCompleted, this, [this]() {
|
||||||
|
emit cancelCompleted();
|
||||||
|
});
|
||||||
|
restore();
|
||||||
|
} else {
|
||||||
|
emit cancelCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool isProcessing() const { return m_isProcessing; }
|
bool isProcessing() const { return m_isProcessing; }
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
|
||||||
|
void previewWallpaper() {
|
||||||
|
const auto* data = m_imageModel.focusedImage();
|
||||||
|
if (data) {
|
||||||
|
m_wallpaperService->preview(*data);
|
||||||
|
} else {
|
||||||
|
emit previewCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|
||||||
void _onSelectCompleted() {
|
void _onSelectCompleted() {
|
||||||
@@ -91,10 +112,13 @@ class Manager : public QObject {
|
|||||||
void selectCompleted();
|
void selectCompleted();
|
||||||
void previewCompleted();
|
void previewCompleted();
|
||||||
void restoreCompleted();
|
void restoreCompleted();
|
||||||
|
void cancelCompleted();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WallpaperService* m_wallpaperService;
|
WallpaperService* m_wallpaperService;
|
||||||
const Image::Model& m_imageModel;
|
const Config::ActionConfigItems& m_actionConfig;
|
||||||
|
Image::Model& m_imageModel;
|
||||||
|
Palette::Manager& m_paletteManager;
|
||||||
|
|
||||||
bool m_isProcessing = false;
|
bool m_isProcessing = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,8 +8,9 @@
|
|||||||
|
|
||||||
WallReel::Core::Service::WallpaperService::WallpaperService(
|
WallReel::Core::Service::WallpaperService::WallpaperService(
|
||||||
const Config::ActionConfigItems& actionConfig,
|
const Config::ActionConfigItems& actionConfig,
|
||||||
|
const Palette::Manager& paletteManager,
|
||||||
QObject* parent)
|
QObject* parent)
|
||||||
: QObject(parent), m_actionConfig(actionConfig) {
|
: QObject(parent), m_actionConfig(actionConfig), m_paletteManager(paletteManager) {
|
||||||
m_previewDebounceTimer = new QTimer(this);
|
m_previewDebounceTimer = new QTimer(this);
|
||||||
m_previewDebounceTimer->setSingleShot(true);
|
m_previewDebounceTimer->setSingleShot(true);
|
||||||
m_previewDebounceTimer->setInterval(m_actionConfig.previewDebounceTime);
|
m_previewDebounceTimer->setInterval(m_actionConfig.previewDebounceTime);
|
||||||
@@ -48,6 +49,22 @@ WallReel::Core::Service::WallpaperService::WallpaperService(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WallReel::Core::Service::WallpaperService::stopAll() {
|
||||||
|
if (m_previewProcess->state() != QProcess::NotRunning) {
|
||||||
|
m_previewProcess->kill();
|
||||||
|
m_previewProcess->waitForFinished();
|
||||||
|
}
|
||||||
|
if (m_selectProcess->state() != QProcess::NotRunning) {
|
||||||
|
m_selectProcess->kill();
|
||||||
|
m_selectProcess->waitForFinished();
|
||||||
|
}
|
||||||
|
if (m_restoreProcess->state() != QProcess::NotRunning) {
|
||||||
|
m_restoreProcess->kill();
|
||||||
|
m_restoreProcess->waitForFinished();
|
||||||
|
}
|
||||||
|
m_previewDebounceTimer->stop();
|
||||||
|
}
|
||||||
|
|
||||||
void WallReel::Core::Service::WallpaperService::preview(const Image::Data& imageData) {
|
void WallReel::Core::Service::WallpaperService::preview(const Image::Data& imageData) {
|
||||||
m_pendingImageData = &imageData;
|
m_pendingImageData = &imageData;
|
||||||
m_previewDebounceTimer->start();
|
m_previewDebounceTimer->start();
|
||||||
@@ -69,6 +86,32 @@ void WallReel::Core::Service::WallpaperService::restore() {
|
|||||||
_doRestore();
|
_doRestore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QHash<QString, QString> WallReel::Core::Service::WallpaperService::_generateVariables(const Image::Data& imageData) {
|
||||||
|
auto palette = m_paletteManager.getSelectedPaletteName();
|
||||||
|
if (palette.isEmpty()) {
|
||||||
|
palette = "null";
|
||||||
|
}
|
||||||
|
auto color = m_paletteManager.getCurrentColorName();
|
||||||
|
if (color.isEmpty()) {
|
||||||
|
color = "null";
|
||||||
|
}
|
||||||
|
auto hex = m_paletteManager.getCurrentColorHex();
|
||||||
|
if (hex.isEmpty()) {
|
||||||
|
hex = "null";
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
{"path", imageData.getFullPath()},
|
||||||
|
{"name", imageData.getFileName()},
|
||||||
|
{"size", QString::number(imageData.getSize())},
|
||||||
|
{"width", QString::number(imageData.getImage().width())},
|
||||||
|
{"height", QString::number(imageData.getImage().height())},
|
||||||
|
{"palette", palette},
|
||||||
|
{"color", color},
|
||||||
|
{"colorHex", hex},
|
||||||
|
{"domColor", m_paletteManager.color().name()},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
void WallReel::Core::Service::WallpaperService::_doPreview(const Image::Data& imageData) {
|
void WallReel::Core::Service::WallpaperService::_doPreview(const Image::Data& imageData) {
|
||||||
QString path = imageData.getFullPath();
|
QString path = imageData.getFullPath();
|
||||||
|
|
||||||
@@ -81,15 +124,13 @@ void WallReel::Core::Service::WallpaperService::_doPreview(const Image::Data& im
|
|||||||
std::cout << path.toStdString() << std::endl;
|
std::cout << path.toStdString() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QHash<QString, QString> variables{
|
const auto variables = _generateVariables(imageData);
|
||||||
{"path", path},
|
|
||||||
{"name", imageData.getFileName()},
|
|
||||||
};
|
|
||||||
auto command = Utils::renderTemplate(m_actionConfig.onPreview, variables);
|
auto command = Utils::renderTemplate(m_actionConfig.onPreview, variables);
|
||||||
if (command.isEmpty()) {
|
if (command.isEmpty()) {
|
||||||
emit previewCompleted();
|
emit previewCompleted();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Logger::debug(QString("Executing preview command: %1").arg(command));
|
||||||
|
|
||||||
if (m_previewProcess->state() != QProcess::NotRunning) {
|
if (m_previewProcess->state() != QProcess::NotRunning) {
|
||||||
m_previewProcess->kill();
|
m_previewProcess->kill();
|
||||||
@@ -110,15 +151,13 @@ void WallReel::Core::Service::WallpaperService::_doSelect(const Image::Data& ima
|
|||||||
std::cout << path.toStdString() << std::endl;
|
std::cout << path.toStdString() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QHash<QString, QString> variables{
|
const auto variables = _generateVariables(imageData);
|
||||||
{"path", path},
|
|
||||||
{"name", imageData.getFileName()},
|
|
||||||
};
|
|
||||||
auto command = Utils::renderTemplate(m_actionConfig.onSelected, variables);
|
auto command = Utils::renderTemplate(m_actionConfig.onSelected, variables);
|
||||||
if (command.isEmpty()) {
|
if (command.isEmpty()) {
|
||||||
emit selectCompleted();
|
emit selectCompleted();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Logger::debug(QString("Executing select command: %1").arg(command));
|
||||||
m_selectProcess->start("sh", QStringList() << "-c" << command);
|
m_selectProcess->start("sh", QStringList() << "-c" << command);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,5 +172,6 @@ void WallReel::Core::Service::WallpaperService::_doRestore() {
|
|||||||
emit restoreCompleted();
|
emit restoreCompleted();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Logger::debug(QString("Executing restore command: %1").arg(command));
|
||||||
m_restoreProcess->start("sh", QStringList() << "-c" << command);
|
m_restoreProcess->start("sh", QStringList() << "-c" << command);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "Config/data.hpp"
|
#include "Config/data.hpp"
|
||||||
#include "Image/data.hpp"
|
#include "Image/data.hpp"
|
||||||
|
#include "Palette/manager.hpp"
|
||||||
|
|
||||||
namespace WallReel::Core::Service {
|
namespace WallReel::Core::Service {
|
||||||
|
|
||||||
@@ -15,8 +16,11 @@ class WallpaperService : public QObject {
|
|||||||
public:
|
public:
|
||||||
WallpaperService(
|
WallpaperService(
|
||||||
const Config::ActionConfigItems& actionConfig,
|
const Config::ActionConfigItems& actionConfig,
|
||||||
|
const Palette::Manager& paletteManager,
|
||||||
QObject* parent = nullptr);
|
QObject* parent = nullptr);
|
||||||
|
|
||||||
|
void stopAll();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void preview(const Image::Data& imageData); // execute after 500ms of inactivity
|
void preview(const Image::Data& imageData); // execute after 500ms of inactivity
|
||||||
void select(const Image::Data& imageData); // execute immediately, ignore if already running
|
void select(const Image::Data& imageData); // execute immediately, ignore if already running
|
||||||
@@ -31,8 +35,10 @@ class WallpaperService : public QObject {
|
|||||||
void _doPreview(const Image::Data& imageData);
|
void _doPreview(const Image::Data& imageData);
|
||||||
void _doSelect(const Image::Data& imageData);
|
void _doSelect(const Image::Data& imageData);
|
||||||
void _doRestore();
|
void _doRestore();
|
||||||
|
QHash<QString, QString> _generateVariables(const Image::Data& imageData);
|
||||||
|
|
||||||
const Config::ActionConfigItems& m_actionConfig;
|
const Config::ActionConfigItems& m_actionConfig;
|
||||||
|
const Palette::Manager& m_paletteManager;
|
||||||
QTimer* m_previewDebounceTimer;
|
QTimer* m_previewDebounceTimer;
|
||||||
const Image::Data* m_pendingImageData;
|
const Image::Data* m_pendingImageData;
|
||||||
QProcess* m_previewProcess;
|
QProcess* m_previewProcess;
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ qt_add_qml_module(${UILIB_NAME}
|
|||||||
Providers/CarouselProvider.qml
|
Providers/CarouselProvider.qml
|
||||||
Modules/Carousel.qml
|
Modules/Carousel.qml
|
||||||
Modules/TitleBar.qml
|
Modules/TitleBar.qml
|
||||||
Modules/SearchBar.qml
|
|
||||||
Modules/SortControl.qml
|
Modules/SortControl.qml
|
||||||
Modules/ColorControl.qml
|
Modules/ColorControl.qml
|
||||||
Modules/TopBar.qml
|
Modules/TopBar.qml
|
||||||
Modules/BottomBar.qml
|
Modules/BottomBar.qml
|
||||||
|
Components/WRSearchBar.qml
|
||||||
Components/WRTextButton.qml
|
Components/WRTextButton.qml
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Item {
|
|||||||
ComboBox {
|
ComboBox {
|
||||||
id: paletteCombo
|
id: paletteCombo
|
||||||
|
|
||||||
implicitWidth: 110
|
implicitWidth: 200
|
||||||
// -1 means nothing selected
|
// -1 means nothing selected
|
||||||
currentIndex: -1
|
currentIndex: -1
|
||||||
displayText: currentIndex < 0 ? "— palette —" : currentText
|
displayText: currentIndex < 0 ? "— palette —" : currentText
|
||||||
@@ -77,7 +77,7 @@ Item {
|
|||||||
if (root.colorHex.length > 0)
|
if (root.colorHex.length > 0)
|
||||||
return root.colorName.length > 0 ? root.colorName + " " + root.colorHex : root.colorHex;
|
return root.colorName.length > 0 ? root.colorName + " " + root.colorHex : root.colorHex;
|
||||||
|
|
||||||
return root.colorName == "Auto" ? "" : root.colorName;
|
return root.colorName;
|
||||||
}
|
}
|
||||||
visible: root.colorName.length > 0 || root.colorHex.length > 0
|
visible: root.colorName.length > 0 || root.colorHex.length > 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import WallReel.UI.Components
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -43,7 +44,7 @@ Item {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchBar {
|
WRSearchBar {
|
||||||
id: searchBar
|
id: searchBar
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ QtObject {
|
|||||||
property string selectedSortType: ImageModel.currentSortType
|
property string selectedSortType: ImageModel.currentSortType
|
||||||
property bool isSortReverse: ImageModel.currentSortReverse
|
property bool isSortReverse: ImageModel.currentSortReverse
|
||||||
//// Palette / Color
|
//// Palette / Color
|
||||||
readonly property var availablePalettes: []
|
readonly property var availablePalettes: PaletteManager.availablePalettes
|
||||||
property var selectedPalette: null // PaletteItem | null
|
property var selectedPalette: null // PaletteItem | null
|
||||||
readonly property var availableColors: selectedPalette ? selectedPalette.colors : []
|
readonly property var availableColors: selectedPalette ? selectedPalette.colors : []
|
||||||
property var selectedColor: null // ColorItem | null (null means "auto")
|
property var selectedColor: null // ColorItem | null (null means "auto")
|
||||||
readonly property string colorName: selectedColor ? selectedColor.name : "Auto"
|
readonly property string colorName: PaletteManager.colorName
|
||||||
readonly property string colorHex: selectedColor ? selectedColor.color.toString().toUpperCase() : ""
|
readonly property string colorHex: PaletteManager.color
|
||||||
readonly property color colorValue: selectedColor ? selectedColor.color : "transparent"
|
readonly property color colorValue: PaletteManager.color
|
||||||
//// Actions state
|
//// Actions state
|
||||||
readonly property bool isProcessing: ServiceManager.isProcessing
|
readonly property bool isProcessing: ServiceManager.isProcessing
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ QtObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cancel() {
|
function cancel() {
|
||||||
Qt.quit();
|
ServiceManager.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
function focusSearch() {
|
function focusSearch() {
|
||||||
@@ -68,19 +68,25 @@ QtObject {
|
|||||||
|
|
||||||
function setSearchText(text) {
|
function setSearchText(text) {
|
||||||
ImageModel.setSearchText(text);
|
ImageModel.setSearchText(text);
|
||||||
currentIndex = 0; // reset index when search text changes
|
if (currentIndex != 0)
|
||||||
|
currentIndex = 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onCurrentIndexChanged: () => {
|
onCurrentIndexChanged: () => {
|
||||||
if (!isLoading) {
|
if (!isLoading)
|
||||||
ServiceManager.previewWallpaper(currentIndex);
|
|
||||||
ImageModel.focusOnIndex(currentIndex);
|
ImageModel.focusOnIndex(currentIndex);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Component.onCompleted: () => {
|
Component.onCompleted: () => {
|
||||||
if (!isLoading) {
|
if (!isLoading)
|
||||||
ServiceManager.previewWallpaper(currentIndex);
|
|
||||||
ImageModel.focusOnIndex(currentIndex);
|
ImageModel.focusOnIndex(currentIndex);
|
||||||
}
|
|
||||||
|
}
|
||||||
|
onSelectedPaletteChanged: () => {
|
||||||
|
PaletteManager.setSelectedPalette(selectedPalette);
|
||||||
|
}
|
||||||
|
onSelectedColorChanged: () => {
|
||||||
|
PaletteManager.setSelectedColor(selectedColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-8
@@ -1,3 +1,5 @@
|
|||||||
|
#include <qobject.h>
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
@@ -46,13 +48,6 @@ int main(int argc, char* argv[]) {
|
|||||||
"Config",
|
"Config",
|
||||||
config);
|
config);
|
||||||
|
|
||||||
auto paletteMgr = new Palette::Manager(
|
|
||||||
config->getPaletteConfig(),
|
|
||||||
&a);
|
|
||||||
engine.rootContext()->setContextProperty("PaletteManager", paletteMgr);
|
|
||||||
qRegisterMetaType<Palette::PaletteItem>("PaletteItem");
|
|
||||||
qRegisterMetaType<Palette::ColorItem>("ColorItem");
|
|
||||||
|
|
||||||
auto imageModel = new Image::Model(
|
auto imageModel = new Image::Model(
|
||||||
*imageProvider,
|
*imageProvider,
|
||||||
config->getSortConfig(),
|
config->getSortConfig(),
|
||||||
@@ -65,10 +60,19 @@ int main(int argc, char* argv[]) {
|
|||||||
"ImageModel",
|
"ImageModel",
|
||||||
imageModel);
|
imageModel);
|
||||||
|
|
||||||
|
auto paletteMgr = new Palette::Manager(
|
||||||
|
config->getPaletteConfig(),
|
||||||
|
*imageModel,
|
||||||
|
imageModel);
|
||||||
|
engine.rootContext()->setContextProperty("PaletteManager", paletteMgr);
|
||||||
|
qRegisterMetaType<Palette::PaletteItem>("PaletteItem");
|
||||||
|
qRegisterMetaType<Palette::ColorItem>("ColorItem");
|
||||||
|
|
||||||
auto Service = new Service::Manager(
|
auto Service = new Service::Manager(
|
||||||
config->getActionConfig(),
|
config->getActionConfig(),
|
||||||
*imageModel,
|
*imageModel,
|
||||||
imageModel);
|
*paletteMgr,
|
||||||
|
paletteMgr);
|
||||||
qmlRegisterSingletonInstance(
|
qmlRegisterSingletonInstance(
|
||||||
COREMODULE_URI,
|
COREMODULE_URI,
|
||||||
MODULE_VERSION_MAJOR,
|
MODULE_VERSION_MAJOR,
|
||||||
@@ -82,6 +86,11 @@ int main(int argc, char* argv[]) {
|
|||||||
&a,
|
&a,
|
||||||
[]() { QCoreApplication::quit(); });
|
[]() { QCoreApplication::quit(); });
|
||||||
}
|
}
|
||||||
|
QObject::connect(
|
||||||
|
Service,
|
||||||
|
&Service::Manager::cancelCompleted,
|
||||||
|
&a,
|
||||||
|
[]() { QCoreApplication::quit(); });
|
||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
&engine,
|
&engine,
|
||||||
@@ -91,6 +100,7 @@ int main(int argc, char* argv[]) {
|
|||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
engine.loadFromModule(UIMODULE_URI, "Main");
|
engine.loadFromModule(UIMODULE_URI, "Main");
|
||||||
|
|
||||||
|
config->captureState();
|
||||||
imageModel->loadAndProcess(config->getWallpapers());
|
imageModel->loadAndProcess(config->getWallpapers());
|
||||||
|
|
||||||
return a.exec();
|
return a.exec();
|
||||||
|
|||||||
Reference in New Issue
Block a user