feat: configurable sizes
This commit is contained in:
@@ -14,5 +14,12 @@
|
|||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"confirm": "~/.scripts/change-wallpaper.fish \"%1\""
|
"confirm": "~/.scripts/change-wallpaper.fish \"%1\""
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"aspect_ratio": 1.6,
|
||||||
|
"image_width": 320,
|
||||||
|
"image_focus_width": 480,
|
||||||
|
"window_width": 750,
|
||||||
|
"window_height": 500
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+103
-44
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-08-05 01:34:52
|
* @Date: 2025-08-05 01:34:52
|
||||||
* @LastEditTime: 2025-08-05 17:26:33
|
* @LastEditTime: 2025-08-05 20:11:11
|
||||||
* @Description: Configuration manager.
|
* @Description: Configuration manager.
|
||||||
*/
|
*/
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
@@ -19,22 +19,6 @@ using namespace GeneralLogger;
|
|||||||
|
|
||||||
static QString expandPath(const QString &path);
|
static QString expandPath(const QString &path);
|
||||||
|
|
||||||
const QString Config::s_DefaultConfigFileName = "config.json";
|
|
||||||
|
|
||||||
Config::Config(const QString &configDir, const QStringList &searchDirs, QObject *parent) : QObject(parent) {
|
|
||||||
info(QString("Loading configuration from: %1").arg(configDir));
|
|
||||||
_loadConfig(configDir + QDir::separator() + s_DefaultConfigFileName);
|
|
||||||
|
|
||||||
info(QString("Additional search directories: %1").arg(searchDirs.join(", ")));
|
|
||||||
m_configItems.wallpaperDirs.append(searchDirs);
|
|
||||||
|
|
||||||
info("Loading wallpapers ...");
|
|
||||||
_loadWallpapers();
|
|
||||||
}
|
|
||||||
|
|
||||||
Config::~Config() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void Config::_loadConfig(const QString &configPath) {
|
void Config::_loadConfig(const QString &configPath) {
|
||||||
QFile configFile(configPath);
|
QFile configFile(configPath);
|
||||||
if (!configFile.open(QIODevice::ReadOnly)) {
|
if (!configFile.open(QIODevice::ReadOnly)) {
|
||||||
@@ -50,42 +34,117 @@ void Config::_loadConfig(const QString &configPath) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const auto parseJsonArray = [](const QJsonObject &obj, const QString &key, QStringList &list) {
|
const auto jsonObj = jsonDoc.object();
|
||||||
if (obj.contains(key) && obj[key].isArray()) {
|
|
||||||
QJsonArray array = obj[key].toArray();
|
struct ConfigMapping {
|
||||||
for (const QJsonValue &value : array) {
|
QString path;
|
||||||
if (value.isString()) {
|
QString key;
|
||||||
list.append(::expandPath(value.toString()));
|
std::function<void(const QJsonValue &)> parser;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const auto parseJsonArray = [](const QJsonValue &val, QStringList &list) {
|
||||||
|
if (val.isArray()) {
|
||||||
|
for (const auto &item : val.toArray()) {
|
||||||
|
if (item.isString()) {
|
||||||
|
list.append(::expandPath(item.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
warn(QString("Key '%1' not found or not an array in config").arg(key));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto jsonObj = jsonDoc.object();
|
std::vector<ConfigMapping>
|
||||||
if (!jsonObj.contains("wallpaper") || !jsonObj["wallpaper"].isObject()) {
|
mappings = {
|
||||||
warn("Key 'wallpaper' not fount or not an object in config");
|
{"wallpaper.paths", "paths", [this](const QJsonValue &val) {
|
||||||
} else {
|
parseJsonArray(val, m_configItems.wallpaperPaths);
|
||||||
const auto wallpaperObj = jsonObj.value("wallpaper").toObject();
|
}},
|
||||||
parseJsonArray(wallpaperObj, "paths", m_configItems.wallpaperPaths);
|
{"wallpaper.dirs", "dirs", [this](const QJsonValue &val) {
|
||||||
parseJsonArray(wallpaperObj, "dirs", m_configItems.wallpaperDirs);
|
parseJsonArray(val, m_configItems.wallpaperDirs);
|
||||||
parseJsonArray(wallpaperObj, "excludes", m_configItems.wallpaperExcludes);
|
}},
|
||||||
}
|
{"wallpaper.excludes", "excludes", [this](const QJsonValue &val) {
|
||||||
|
parseJsonArray(val, m_configItems.wallpaperExcludes);
|
||||||
|
}},
|
||||||
|
{"actions.confirm", "confirm", [this](const QJsonValue &val) {
|
||||||
|
if (val.isString()) {
|
||||||
|
m_configItems.actionsConfirm = ::expandPath(val.toString());
|
||||||
|
info(QString("Action confirm: %1").arg(m_configItems.actionsConfirm));
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
{"style.aspect_ratio", "aspect_ratio", [this](const QJsonValue &val) {
|
||||||
|
if (val.isDouble() && val.toDouble() > 0) {
|
||||||
|
m_configItems.styleAspectRatio = val.toDouble();
|
||||||
|
info(QString("Aspect ratio: %1").arg(m_configItems.styleAspectRatio));
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
{"style.image_width", "image_width", [this](const QJsonValue &val) {
|
||||||
|
if (val.isDouble() && val.toDouble() > 0) {
|
||||||
|
m_configItems.styleImageWidth = val.toInt();
|
||||||
|
info(QString("Image width: %1").arg(m_configItems.styleImageWidth));
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
{"style.image_focus_width", "image_focus_width", [this](const QJsonValue &val) {
|
||||||
|
if (val.isDouble() && val.toDouble() > 0) {
|
||||||
|
m_configItems.styleImageFocusWidth = val.toInt();
|
||||||
|
info(QString("Image focus width: %1").arg(m_configItems.styleImageFocusWidth));
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
{"style.window_width", "window_width", [this](const QJsonValue &val) {
|
||||||
|
if (val.isDouble() && val.toDouble() > 0) {
|
||||||
|
m_configItems.styleWindowWidth = val.toInt();
|
||||||
|
info(QString("Window width: %1").arg(m_configItems.styleWindowWidth));
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
{"style.window_height", "window_height", [this](const QJsonValue &val) {
|
||||||
|
if (val.isDouble() && val.toDouble() > 0) {
|
||||||
|
m_configItems.styleWindowHeight = val.toInt();
|
||||||
|
info(QString("Window height: %1").arg(m_configItems.styleWindowHeight));
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
};
|
||||||
|
|
||||||
if (!jsonObj.contains("actions") || !jsonObj["actions"].isObject()) {
|
// 统一解析
|
||||||
warn("Key 'actions' not found or not an object in config");
|
for (const auto &mapping : mappings) {
|
||||||
} else {
|
([&mapping, &jsonObj]() {
|
||||||
const auto actionsObj = jsonObj.value("actions").toObject();
|
auto pathParts = mapping.path.split('.');
|
||||||
if (actionsObj.contains("confirm") && actionsObj["confirm"].isString()) {
|
|
||||||
m_configItems.actionsConfirm = ::expandPath(actionsObj["confirm"].toString());
|
QJsonObject currentObj = jsonObj;
|
||||||
info(QString("Action on confirm: %1").arg(m_configItems.actionsConfirm));
|
QJsonValue targetValue;
|
||||||
} else {
|
|
||||||
warn("Key 'confirm' not found or not a string in 'actions'");
|
for (int i = 0; i < pathParts.size() - 1; ++i) {
|
||||||
}
|
if (currentObj.contains(pathParts[i]) && currentObj[pathParts[i]].isObject()) {
|
||||||
|
currentObj = currentObj[pathParts[i]].toObject();
|
||||||
|
} else {
|
||||||
|
warn(QString("Path '%1' not found").arg(pathParts.mid(0, i + 1).join('.')));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取目标值
|
||||||
|
const QString &finalKey = pathParts.last();
|
||||||
|
if (currentObj.contains(finalKey)) {
|
||||||
|
mapping.parser(currentObj[finalKey]);
|
||||||
|
} else {
|
||||||
|
warn(QString("Key '%1' not found in '%2'").arg(finalKey).arg(mapping.path));
|
||||||
|
}
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QString Config::s_DefaultConfigFileName = "config.json";
|
||||||
|
|
||||||
|
Config::Config(const QString &configDir, const QStringList &searchDirs, QObject *parent) : QObject(parent) {
|
||||||
|
info(QString("Loading configuration from: %1").arg(configDir));
|
||||||
|
_loadConfig(configDir + QDir::separator() + s_DefaultConfigFileName);
|
||||||
|
|
||||||
|
info(QString("Additional search directories: %1").arg(searchDirs.join(", ")));
|
||||||
|
m_configItems.wallpaperDirs.append(searchDirs);
|
||||||
|
|
||||||
|
info("Loading wallpapers ...");
|
||||||
|
_loadWallpapers();
|
||||||
|
}
|
||||||
|
|
||||||
|
Config::~Config() {
|
||||||
|
}
|
||||||
|
|
||||||
void Config::_loadWallpapers() {
|
void Config::_loadWallpapers() {
|
||||||
m_wallpapers.clear();
|
m_wallpapers.clear();
|
||||||
|
|
||||||
|
|||||||
+17
-2
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-08-05 01:34:52
|
* @Date: 2025-08-05 01:34:52
|
||||||
* @LastEditTime: 2025-08-05 17:26:25
|
* @LastEditTime: 2025-08-05 20:12:47
|
||||||
* @Description: Configuration manager.
|
* @Description: Configuration manager.
|
||||||
*/
|
*/
|
||||||
#ifndef CONFIG_H
|
#ifndef CONFIG_H
|
||||||
@@ -27,6 +27,16 @@ class Config : public QObject {
|
|||||||
|
|
||||||
[[nodiscard]] const QString& getActionsConfirm() const { return m_configItems.actionsConfirm; }
|
[[nodiscard]] const QString& getActionsConfirm() const { return m_configItems.actionsConfirm; }
|
||||||
|
|
||||||
|
[[nodiscard]] double getStyleAspectRatio() const { return m_configItems.styleAspectRatio; }
|
||||||
|
|
||||||
|
[[nodiscard]] int getStyleImageWidth() const { return m_configItems.styleImageWidth; }
|
||||||
|
|
||||||
|
[[nodiscard]] int getStyleImageFocusWidth() const { return m_configItems.styleImageFocusWidth; }
|
||||||
|
|
||||||
|
[[nodiscard]] int getStyleWindowWidth() const { return m_configItems.styleWindowWidth; }
|
||||||
|
|
||||||
|
[[nodiscard]] int getStyleWindowHeight() const { return m_configItems.styleWindowHeight; }
|
||||||
|
|
||||||
static const QString s_DefaultConfigFileName;
|
static const QString s_DefaultConfigFileName;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -35,11 +45,16 @@ class Config : public QObject {
|
|||||||
void _loadWallpapers();
|
void _loadWallpapers();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ConfigItems {
|
struct _ConfigItems {
|
||||||
QStringList wallpaperPaths;
|
QStringList wallpaperPaths;
|
||||||
QStringList wallpaperDirs;
|
QStringList wallpaperDirs;
|
||||||
QStringList wallpaperExcludes;
|
QStringList wallpaperExcludes;
|
||||||
QString actionsConfirm;
|
QString actionsConfirm;
|
||||||
|
double styleAspectRatio = 1.6;
|
||||||
|
int styleImageWidth = 320;
|
||||||
|
int styleImageFocusWidth = 480;
|
||||||
|
int styleWindowWidth = 800;
|
||||||
|
int styleWindowHeight = 600;
|
||||||
} m_configItems;
|
} m_configItems;
|
||||||
|
|
||||||
QStringList m_wallpapers;
|
QStringList m_wallpapers;
|
||||||
|
|||||||
@@ -10,6 +10,12 @@
|
|||||||
<height>300</height>
|
<height>300</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
|
|||||||
@@ -35,17 +35,7 @@
|
|||||||
<set>QMainWindow::DockOption::AllowTabbedDocks|QMainWindow::DockOption::AnimatedDocks</set>
|
<set>QMainWindow::DockOption::AllowTabbedDocks|QMainWindow::DockOption::AnimatedDocks</set>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="centralwidget">
|
<widget class="QWidget" name="centralwidget">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="mainLayout">
|
||||||
<item>
|
|
||||||
<widget class="ImagesCarousel" name="carousel" native="true">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QWidget" name="actions" native="true">
|
<widget class="QWidget" name="actions" native="true">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
@@ -100,14 +90,6 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
|
||||||
<customwidget>
|
|
||||||
<class>ImagesCarousel</class>
|
|
||||||
<extends>QWidget</extends>
|
|
||||||
<header>images_carousel.h</header>
|
|
||||||
<container>1</container>
|
|
||||||
</customwidget>
|
|
||||||
</customwidgets>
|
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
|||||||
+54
-53
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-08-05 01:22:53
|
* @Date: 2025-08-05 01:22:53
|
||||||
* @LastEditTime: 2025-08-05 17:25:59
|
* @LastEditTime: 2025-08-05 20:06:23
|
||||||
* @Description: Animated carousel widget for displaying and selecting images.
|
* @Description: Animated carousel widget for displaying and selecting images.
|
||||||
*/
|
*/
|
||||||
#include "images_carousel.h"
|
#include "images_carousel.h"
|
||||||
@@ -21,20 +21,27 @@
|
|||||||
|
|
||||||
using namespace GeneralLogger;
|
using namespace GeneralLogger;
|
||||||
|
|
||||||
ImagesCarousel::ImagesCarousel(QWidget* parent)
|
ImagesCarousel::ImagesCarousel(const double itemAspectRatio,
|
||||||
|
const int itemWidth,
|
||||||
|
const int itemFocusWidth,
|
||||||
|
QWidget* parent)
|
||||||
: QWidget(parent),
|
: QWidget(parent),
|
||||||
ui(new Ui::ImagesCarousel),
|
ui(new Ui::ImagesCarousel),
|
||||||
m_updateTimer(new QTimer(this)),
|
m_updateTimer(new QTimer(this)),
|
||||||
m_scrollAnimation(nullptr) {
|
m_scrollAnimation(nullptr),
|
||||||
|
m_itemWidth(itemWidth),
|
||||||
|
m_itemHeight(static_cast<int>(itemWidth / itemAspectRatio)),
|
||||||
|
m_itemFocusWidth(itemFocusWidth),
|
||||||
|
m_itemFocusHeight(static_cast<int>(itemFocusWidth / itemAspectRatio)) {
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
connect(m_updateTimer, &QTimer::timeout, this, &ImagesCarousel::updateImages);
|
connect(m_updateTimer, &QTimer::timeout, this, &ImagesCarousel::_updateImages);
|
||||||
m_updateTimer->start(100);
|
m_updateTimer->start(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImagesCarousel::~ImagesCarousel() {
|
ImagesCarousel::~ImagesCarousel() {
|
||||||
delete ui;
|
delete ui;
|
||||||
for (auto item : m_imageQueue) {
|
for (auto item : std::as_const(m_imageQueue)) {
|
||||||
delete item;
|
delete item;
|
||||||
}
|
}
|
||||||
if (m_scrollAnimation) {
|
if (m_scrollAnimation) {
|
||||||
@@ -49,22 +56,26 @@ void ImagesCarousel::appendImage(const QString& path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImageLoader::ImageLoader(const QString& path, ImagesCarousel* carousel)
|
ImageLoader::ImageLoader(const QString& path, ImagesCarousel* carousel)
|
||||||
: m_path(path), m_carousel(carousel) {
|
: m_path(path),
|
||||||
|
m_carousel(carousel),
|
||||||
|
m_initWidth(carousel->m_itemFocusWidth),
|
||||||
|
m_initHeight(carousel->m_itemFocusHeight) {
|
||||||
setAutoDelete(true);
|
setAutoDelete(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImagesCarousel::addImageToQueue(const ImageData* data) {
|
void ImagesCarousel::_addImageToQueue(const ImageData* data) {
|
||||||
QMutexLocker locker(&m_queueMutex);
|
QMutexLocker locker(&m_queueMutex);
|
||||||
auto imageItem = new ImageItem(data,
|
auto imageItem = new ImageItem(
|
||||||
s_itemWidth,
|
data,
|
||||||
s_itemHeight,
|
m_itemWidth,
|
||||||
s_itemFocusWidth,
|
m_itemHeight,
|
||||||
s_itemFocusHeight,
|
m_itemFocusWidth,
|
||||||
this);
|
m_itemFocusHeight,
|
||||||
|
this);
|
||||||
m_imageQueue.enqueue(imageItem);
|
m_imageQueue.enqueue(imageItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImagesCarousel::updateImages() {
|
void ImagesCarousel::_updateImages() {
|
||||||
QMutexLocker locker(&m_queueMutex);
|
QMutexLocker locker(&m_queueMutex);
|
||||||
|
|
||||||
int processCount = 0;
|
int processCount = 0;
|
||||||
@@ -72,30 +83,31 @@ void ImagesCarousel::updateImages() {
|
|||||||
ImageItem* item = m_imageQueue.dequeue();
|
ImageItem* item = m_imageQueue.dequeue();
|
||||||
ui->scrollAreaWidgetContents->layout()->addWidget(item);
|
ui->scrollAreaWidgetContents->layout()->addWidget(item);
|
||||||
m_loadedImages.append(item);
|
m_loadedImages.append(item);
|
||||||
|
// focus first image
|
||||||
if (m_loadedImages.size() == 1) {
|
if (m_loadedImages.size() == 1) {
|
||||||
item->focusImage();
|
item->setFocus(true);
|
||||||
} else {
|
} else {
|
||||||
item->unfocusImage();
|
item->setFocus(false);
|
||||||
}
|
}
|
||||||
processCount++;
|
processCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageLoader::run() {
|
void ImageLoader::run() {
|
||||||
auto data = new ImageData(m_path);
|
auto data = new ImageData(m_path, m_initWidth, m_initHeight);
|
||||||
QMetaObject::invokeMethod(m_carousel,
|
QMetaObject::invokeMethod(m_carousel,
|
||||||
"addImageToQueue",
|
"_addImageToQueue",
|
||||||
Qt::QueuedConnection,
|
Qt::QueuedConnection,
|
||||||
Q_ARG(const ImageData*, data));
|
Q_ARG(const ImageData*, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageData::ImageData(const QString& p) : path(p) {
|
ImageData::ImageData(const QString& p, const int initWidth, const int initHeight) : path(p) {
|
||||||
path = p;
|
path = p;
|
||||||
if (!pixmap.load(p)) {
|
if (!pixmap.load(p)) {
|
||||||
warn(QString("Failed to load image from path: %1").arg(p));
|
warn(QString("Failed to load image from path: %1").arg(p));
|
||||||
}
|
}
|
||||||
// resize in "cover" mode
|
// resize in "cover" mode
|
||||||
const QSize targetSize(ImagesCarousel::s_itemWidth, ImagesCarousel::s_itemHeight);
|
const QSize targetSize(initWidth, initHeight);
|
||||||
pixmap = pixmap.scaled(targetSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
pixmap = pixmap.scaled(targetSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
||||||
|
|
||||||
// Crop to center
|
// Crop to center
|
||||||
@@ -105,34 +117,34 @@ ImageData::ImageData(const QString& p) : path(p) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ImagesCarousel::focusNextImage() {
|
void ImagesCarousel::focusNextImage() {
|
||||||
unfocusCurrImage();
|
_unfocusCurrImage();
|
||||||
if (m_loadedImages.size() <= 1) return;
|
if (m_loadedImages.size() <= 1) return;
|
||||||
m_currentIndex++;
|
m_currentIndex++;
|
||||||
if (m_currentIndex >= m_loadedImages.size()) {
|
if (m_currentIndex >= m_loadedImages.size()) {
|
||||||
m_currentIndex = 0;
|
m_currentIndex = 0;
|
||||||
}
|
}
|
||||||
focusCurrImage();
|
_focusCurrImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImagesCarousel::focusPrevImage() {
|
void ImagesCarousel::focusPrevImage() {
|
||||||
if (m_loadedImages.size() <= 1) return;
|
if (m_loadedImages.size() <= 1) return;
|
||||||
unfocusCurrImage();
|
_unfocusCurrImage();
|
||||||
m_currentIndex--;
|
m_currentIndex--;
|
||||||
if (m_currentIndex < 0) {
|
if (m_currentIndex < 0) {
|
||||||
m_currentIndex = m_loadedImages.size() - 1;
|
m_currentIndex = m_loadedImages.size() - 1;
|
||||||
}
|
}
|
||||||
focusCurrImage();
|
_focusCurrImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImagesCarousel::unfocusCurrImage() {
|
void ImagesCarousel::_unfocusCurrImage() {
|
||||||
m_loadedImages[m_currentIndex]->unfocusImage();
|
m_loadedImages[m_currentIndex]->setFocus(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImagesCarousel::focusCurrImage() {
|
void ImagesCarousel::_focusCurrImage() {
|
||||||
m_loadedImages[m_currentIndex]->focusImage();
|
m_loadedImages[m_currentIndex]->setFocus(true);
|
||||||
auto hScrollBar = ui->scrollArea->horizontalScrollBar();
|
auto hScrollBar = ui->scrollArea->horizontalScrollBar();
|
||||||
int spacing = ui->scrollAreaWidgetContents->layout()->spacing();
|
int spacing = ui->scrollAreaWidgetContents->layout()->spacing();
|
||||||
int centerOffset = (s_itemWidth + spacing) * m_currentIndex + s_itemFocusWidth / 2 - spacing;
|
int centerOffset = (m_itemWidth + spacing) * m_currentIndex + m_itemFocusWidth / 2 - spacing;
|
||||||
int leftOffset = centerOffset - ui->scrollArea->width() / 2;
|
int leftOffset = centerOffset - ui->scrollArea->width() / 2;
|
||||||
if (leftOffset < 0) {
|
if (leftOffset < 0) {
|
||||||
leftOffset = 0;
|
leftOffset = 0;
|
||||||
@@ -162,12 +174,21 @@ ImageItem::ImageItem(const ImageData* data,
|
|||||||
m_data(data),
|
m_data(data),
|
||||||
m_itemSize(itemWidth, itemHeight),
|
m_itemSize(itemWidth, itemHeight),
|
||||||
m_itemFocusSize(itemFocusWidth, itemFocusHeight) {
|
m_itemFocusSize(itemFocusWidth, itemFocusHeight) {
|
||||||
setPixmap(data->pixmap);
|
|
||||||
setFixedSize(ImagesCarousel::s_itemWidth, ImagesCarousel::s_itemHeight);
|
|
||||||
setScaledContents(true);
|
setScaledContents(true);
|
||||||
|
setPixmap(data->pixmap);
|
||||||
|
setFixedSize(itemWidth, itemHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageItem::focusImage() {
|
ImageItem::~ImageItem() {
|
||||||
|
if (m_scaleAnimation) {
|
||||||
|
m_scaleAnimation->stop();
|
||||||
|
delete m_scaleAnimation;
|
||||||
|
m_scaleAnimation = nullptr;
|
||||||
|
}
|
||||||
|
delete m_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageItem::setFocus(bool focus) {
|
||||||
if (m_scaleAnimation) {
|
if (m_scaleAnimation) {
|
||||||
m_scaleAnimation->stop();
|
m_scaleAnimation->stop();
|
||||||
delete m_scaleAnimation;
|
delete m_scaleAnimation;
|
||||||
@@ -176,27 +197,7 @@ void ImageItem::focusImage() {
|
|||||||
m_scaleAnimation = new QPropertyAnimation(this, "size");
|
m_scaleAnimation = new QPropertyAnimation(this, "size");
|
||||||
m_scaleAnimation->setDuration(ImagesCarousel::s_animationDuration);
|
m_scaleAnimation->setDuration(ImagesCarousel::s_animationDuration);
|
||||||
m_scaleAnimation->setStartValue(size());
|
m_scaleAnimation->setStartValue(size());
|
||||||
m_scaleAnimation->setEndValue(m_itemFocusSize);
|
m_scaleAnimation->setEndValue(focus ? m_itemFocusSize : m_itemSize);
|
||||||
m_scaleAnimation->setEasingCurve(QEasingCurve::OutCubic);
|
|
||||||
connect(m_scaleAnimation,
|
|
||||||
&QPropertyAnimation::valueChanged,
|
|
||||||
this,
|
|
||||||
[this](const QVariant& value) {
|
|
||||||
setFixedSize(value.toSize());
|
|
||||||
});
|
|
||||||
m_scaleAnimation->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImageItem::unfocusImage() {
|
|
||||||
if (m_scaleAnimation) {
|
|
||||||
m_scaleAnimation->stop();
|
|
||||||
delete m_scaleAnimation;
|
|
||||||
m_scaleAnimation = nullptr;
|
|
||||||
}
|
|
||||||
m_scaleAnimation = new QPropertyAnimation(this, "size");
|
|
||||||
m_scaleAnimation->setDuration(ImagesCarousel::s_animationDuration);
|
|
||||||
m_scaleAnimation->setStartValue(size());
|
|
||||||
m_scaleAnimation->setEndValue(m_itemSize);
|
|
||||||
m_scaleAnimation->setEasingCurve(QEasingCurve::OutCubic);
|
m_scaleAnimation->setEasingCurve(QEasingCurve::OutCubic);
|
||||||
connect(m_scaleAnimation,
|
connect(m_scaleAnimation,
|
||||||
&QPropertyAnimation::valueChanged,
|
&QPropertyAnimation::valueChanged,
|
||||||
|
|||||||
+37
-17
@@ -1,12 +1,14 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-08-05 01:22:53
|
* @Date: 2025-08-05 01:22:53
|
||||||
* @LastEditTime: 2025-08-05 17:25:34
|
* @LastEditTime: 2025-08-05 19:47:43
|
||||||
* @Description: Animated carousel widget for displaying and selecting images.
|
* @Description: Animated carousel widget for displaying and selecting images.
|
||||||
*/
|
*/
|
||||||
#ifndef IMAGES_CAROUSEL_H
|
#ifndef IMAGES_CAROUSEL_H
|
||||||
#define IMAGES_CAROUSEL_H
|
#define IMAGES_CAROUSEL_H
|
||||||
|
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
@@ -22,15 +24,20 @@
|
|||||||
|
|
||||||
class ImagesCarousel;
|
class ImagesCarousel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Data structure to hold image information
|
||||||
|
* and can be safely created and passed between threads.
|
||||||
|
*/
|
||||||
struct ImageData {
|
struct ImageData {
|
||||||
QString path;
|
QString path;
|
||||||
QPixmap pixmap;
|
QPixmap pixmap;
|
||||||
|
|
||||||
ImageData() = default;
|
explicit ImageData(const QString& p, const int initWidth, const int initHeight);
|
||||||
|
|
||||||
explicit ImageData(const QString& p);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Image label that displays an image
|
||||||
|
*/
|
||||||
class ImageItem : public QLabel {
|
class ImageItem : public QLabel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@@ -42,13 +49,14 @@ class ImageItem : public QLabel {
|
|||||||
const int itemFocusHeight,
|
const int itemFocusHeight,
|
||||||
QWidget* parent = nullptr);
|
QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
~ImageItem() override;
|
||||||
|
|
||||||
[[nodiscard]] const QString& getPath() const { return m_data->path; }
|
[[nodiscard]] const QString& getPath() const { return m_data->path; }
|
||||||
|
|
||||||
[[nodiscard]] const QPixmap& getPixmap() const { return m_data->pixmap; }
|
[[nodiscard]] const QPixmap& getPixmap() const { return m_data->pixmap; }
|
||||||
|
|
||||||
public slots:
|
public:
|
||||||
void focusImage();
|
void setFocus(bool focus = true);
|
||||||
void unfocusImage();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const ImageData* m_data;
|
const ImageData* m_data;
|
||||||
@@ -65,6 +73,8 @@ class ImageLoader : public QRunnable {
|
|||||||
private:
|
private:
|
||||||
QString m_path;
|
QString m_path;
|
||||||
ImagesCarousel* m_carousel;
|
ImagesCarousel* m_carousel;
|
||||||
|
const int m_initWidth;
|
||||||
|
const int m_initHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
@@ -74,14 +84,15 @@ class ImagesCarousel;
|
|||||||
class ImagesCarousel : public QWidget {
|
class ImagesCarousel : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
friend void ImageLoader::run();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ImagesCarousel(QWidget* parent = nullptr);
|
explicit ImagesCarousel(const double itemAspectRatio,
|
||||||
|
const int itemWidth,
|
||||||
|
const int itemFocusWidth,
|
||||||
|
QWidget* parent = nullptr);
|
||||||
~ImagesCarousel();
|
~ImagesCarousel();
|
||||||
|
|
||||||
static constexpr int s_itemWidth = 320;
|
|
||||||
static constexpr int s_itemHeight = 200;
|
|
||||||
static constexpr int s_itemFocusWidth = 480;
|
|
||||||
static constexpr int s_itemFocusHeight = 300;
|
|
||||||
static constexpr int s_animationDuration = 300;
|
static constexpr int s_animationDuration = 300;
|
||||||
|
|
||||||
[[nodiscard]] QString getCurrentImagePath() const {
|
[[nodiscard]] QString getCurrentImagePath() const {
|
||||||
@@ -91,16 +102,25 @@ class ImagesCarousel : public QWidget {
|
|||||||
return m_loadedImages[m_currentIndex]->getPath();
|
return m_loadedImages[m_currentIndex]->getPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const int m_itemWidth = 320;
|
||||||
|
const int m_itemHeight = 180;
|
||||||
|
const int m_itemFocusWidth = 480;
|
||||||
|
const int m_itemFocusHeight = 270;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void addImageToQueue(const ImageData* data);
|
|
||||||
void appendImage(const QString& path);
|
|
||||||
void focusNextImage();
|
void focusNextImage();
|
||||||
void focusPrevImage();
|
void focusPrevImage();
|
||||||
void unfocusCurrImage();
|
|
||||||
void focusCurrImage();
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void updateImages();
|
void _unfocusCurrImage();
|
||||||
|
|
||||||
|
public:
|
||||||
|
void appendImage(const QString& path);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Q_INVOKABLE void _addImageToQueue(const ImageData* data);
|
||||||
|
void _focusCurrImage();
|
||||||
|
void _updateImages();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::ImagesCarousel* ui;
|
Ui::ImagesCarousel* ui;
|
||||||
|
|||||||
+5
-2
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-08-05 00:37:58
|
* @Date: 2025-08-05 00:37:58
|
||||||
* @LastEditTime: 2025-08-05 17:34:37
|
* @LastEditTime: 2025-08-05 19:42:07
|
||||||
* @Description: Entry point.
|
* @Description: Entry point.
|
||||||
*/
|
*/
|
||||||
#include <qapplication.h>
|
#include <qapplication.h>
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "main_window.h"
|
#include "main_window.h"
|
||||||
|
|
||||||
@@ -28,7 +29,9 @@ static QString getConfigDir() {
|
|||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
QApplication a(argc, argv);
|
QApplication a(argc, argv);
|
||||||
|
|
||||||
MainWindow w(::getConfigDir());
|
Config config(getConfigDir());
|
||||||
|
|
||||||
|
MainWindow w(config);
|
||||||
w.show();
|
w.show();
|
||||||
|
|
||||||
return a.exec();
|
return a.exec();
|
||||||
|
|||||||
+21
-12
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-08-05 00:37:58
|
* @Date: 2025-08-05 00:37:58
|
||||||
* @LastEditTime: 2025-08-05 17:40:35
|
* @LastEditTime: 2025-08-05 20:12:40
|
||||||
* @Description: MainWindow implementation.
|
* @Description: MainWindow implementation.
|
||||||
*/
|
*/
|
||||||
#include "main_window.h"
|
#include "main_window.h"
|
||||||
@@ -17,11 +17,8 @@
|
|||||||
|
|
||||||
using namespace GeneralLogger;
|
using namespace GeneralLogger;
|
||||||
|
|
||||||
MainWindow::MainWindow(const QString &configDir, QWidget *parent)
|
MainWindow::MainWindow(const Config &config, QWidget *parent)
|
||||||
: QMainWindow(parent), ui(new Ui::MainWindow) {
|
: QMainWindow(parent), ui(new Ui::MainWindow), m_config(config) {
|
||||||
|
|
||||||
m_config = new Config(configDir, {}, this);
|
|
||||||
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
_setupUI();
|
_setupUI();
|
||||||
}
|
}
|
||||||
@@ -31,13 +28,25 @@ MainWindow::~MainWindow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::_setupUI() {
|
void MainWindow::_setupUI() {
|
||||||
|
// insert images carousel
|
||||||
|
m_carousel = new ImagesCarousel(
|
||||||
|
m_config.getStyleAspectRatio(),
|
||||||
|
m_config.getStyleImageWidth(),
|
||||||
|
m_config.getStyleImageFocusWidth(),
|
||||||
|
this);
|
||||||
|
ui->mainLayout->insertWidget(0, m_carousel);
|
||||||
|
|
||||||
|
// set window size
|
||||||
|
setMinimumSize(m_config.getStyleWindowWidth(), m_config.getStyleWindowHeight());
|
||||||
|
setMaximumSize(m_config.getStyleWindowWidth(), m_config.getStyleWindowHeight());
|
||||||
|
|
||||||
connect(ui->confirmButton, &QPushButton::clicked, this, &MainWindow::onConfirm);
|
connect(ui->confirmButton, &QPushButton::clicked, this, &MainWindow::onConfirm);
|
||||||
connect(ui->cancelButton, &QPushButton::clicked, this, &MainWindow::onCancel);
|
connect(ui->cancelButton, &QPushButton::clicked, this, &MainWindow::onCancel);
|
||||||
ui->confirmButton->setFocusPolicy(Qt::NoFocus);
|
ui->confirmButton->setFocusPolicy(Qt::NoFocus);
|
||||||
ui->cancelButton->setFocusPolicy(Qt::NoFocus);
|
ui->cancelButton->setFocusPolicy(Qt::NoFocus);
|
||||||
|
|
||||||
for (const auto &image : m_config->getWallpapers()) {
|
for (const auto &image : m_config.getWallpapers()) {
|
||||||
ui->carousel->appendImage(image);
|
m_carousel->appendImage(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,9 +56,9 @@ void MainWindow::keyPressEvent(QKeyEvent *event) {
|
|||||||
} else if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
|
} else if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
|
||||||
onConfirm();
|
onConfirm();
|
||||||
} else if (event->key() == Qt::Key_Space || event->key() == Qt::Key_Tab || event->key() == Qt::Key_Right) {
|
} else if (event->key() == Qt::Key_Space || event->key() == Qt::Key_Tab || event->key() == Qt::Key_Right) {
|
||||||
ui->carousel->focusNextImage();
|
m_carousel->focusNextImage();
|
||||||
} else if (event->key() == Qt::Key_Left) {
|
} else if (event->key() == Qt::Key_Left) {
|
||||||
ui->carousel->focusPrevImage();
|
m_carousel->focusPrevImage();
|
||||||
} else {
|
} else {
|
||||||
QMainWindow::keyPressEvent(event);
|
QMainWindow::keyPressEvent(event);
|
||||||
}
|
}
|
||||||
@@ -57,13 +66,13 @@ void MainWindow::keyPressEvent(QKeyEvent *event) {
|
|||||||
|
|
||||||
void MainWindow::onConfirm() {
|
void MainWindow::onConfirm() {
|
||||||
close();
|
close();
|
||||||
const auto path = ui->carousel->getCurrentImagePath();
|
const auto path = m_carousel->getCurrentImagePath();
|
||||||
if (path.isEmpty()) {
|
if (path.isEmpty()) {
|
||||||
warn("No image selected");
|
warn("No image selected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
info(QString("Selected image: %1").arg(path));
|
info(QString("Selected image: %1").arg(path));
|
||||||
const auto cmdOrig = m_config->getActionsConfirm();
|
const auto cmdOrig = m_config.getActionsConfirm();
|
||||||
if (cmdOrig.isEmpty()) {
|
if (cmdOrig.isEmpty()) {
|
||||||
warn("No action defined for confirmation");
|
warn("No action defined for confirmation");
|
||||||
return;
|
return;
|
||||||
|
|||||||
+5
-3
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-08-05 00:37:58
|
* @Date: 2025-08-05 00:37:58
|
||||||
* @LastEditTime: 2025-08-05 17:23:41
|
* @LastEditTime: 2025-08-05 19:53:51
|
||||||
* @Description: MainWindow implementation.
|
* @Description: MainWindow implementation.
|
||||||
*/
|
*/
|
||||||
#ifndef MAINWINDOW_H
|
#ifndef MAINWINDOW_H
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "images_carousel.h"
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ class MainWindow : public QMainWindow {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MainWindow(const QString &configDir, QWidget *parent = nullptr);
|
MainWindow(const Config &config, QWidget *parent = nullptr);
|
||||||
~MainWindow();
|
~MainWindow();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
@@ -38,6 +39,7 @@ class MainWindow : public QMainWindow {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::MainWindow *ui;
|
Ui::MainWindow *ui;
|
||||||
Config *m_config;
|
ImagesCarousel *m_carousel = nullptr;
|
||||||
|
const Config &m_config;
|
||||||
};
|
};
|
||||||
#endif // MAINWINDOW_H
|
#endif // MAINWINDOW_H
|
||||||
|
|||||||
Reference in New Issue
Block a user