feat: sorting

This commit is contained in:
2025-08-06 00:52:49 +02:00
parent 1100910f62
commit f026f12e4c
8 changed files with 190 additions and 38 deletions
+4
View File
@@ -21,5 +21,9 @@
"image_focus_width": 480, "image_focus_width": 480,
"window_width": 750, "window_width": 750,
"window_height": 500 "window_height": 500
},
"sort": {
"type": "size",
"reverse": false
} }
} }
+25 -1
View File
@@ -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 20:11:11 * @LastEditTime: 2025-08-06 00:23:54
* @Description: Configuration manager. * @Description: Configuration manager.
*/ */
#include "config.h" #include "config.h"
@@ -99,6 +99,29 @@ void Config::_loadConfig(const QString &configPath) {
info(QString("Window height: %1").arg(m_configItems.styleWindowHeight)); info(QString("Window height: %1").arg(m_configItems.styleWindowHeight));
} }
}}, }},
{"sort.type", "type", [this](const QJsonValue &val) {
if (val.isString()) {
QString type = val.toString().toLower();
if (type == "none") {
m_configItems.sortType = SortType::None;
} else if (type == "name") {
m_configItems.sortType = SortType::Name;
} else if (type == "date") {
m_configItems.sortType = SortType::Date;
} else if (type == "size") {
m_configItems.sortType = SortType::Size;
} else {
warn(QString("Unknown sort type: %1").arg(type));
}
}
info(QString("Sort type: %1").arg(static_cast<int>(m_configItems.sortType)));
}},
{"sort.reverse", "reverse", [this](const QJsonValue &val) {
if (val.isBool()) {
m_configItems.sortReverse = val.toBool();
info(QString("Sort reverse: %1").arg(m_configItems.sortReverse));
}
}},
}; };
// 统一解析 // 统一解析
@@ -180,6 +203,7 @@ void Config::_loadWallpapers() {
m_wallpapers.append(path); m_wallpapers.append(path);
} }
} }
info(QString("Found %1 wallpapers").arg(paths.size())); info(QString("Found %1 wallpapers").arg(paths.size()));
} }
+16 -3
View File
@@ -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 20:12:47 * @LastEditTime: 2025-08-06 00:23:45
* @Description: Configuration manager. * @Description: Configuration manager.
*/ */
#ifndef CONFIG_H #ifndef CONFIG_H
@@ -15,6 +15,13 @@ class Config : public QObject {
Q_OBJECT Q_OBJECT
public: public:
enum class SortType : int {
None = 0,
Name,
Date,
Size,
};
Config(const QString& configDir, const QStringList& searchDirs = {}, QObject* parent = nullptr); Config(const QString& configDir, const QStringList& searchDirs = {}, QObject* parent = nullptr);
~Config(); ~Config();
@@ -37,6 +44,10 @@ class Config : public QObject {
[[nodiscard]] int getStyleWindowHeight() const { return m_configItems.styleWindowHeight; } [[nodiscard]] int getStyleWindowHeight() const { return m_configItems.styleWindowHeight; }
[[nodiscard]] SortType getSortType() const { return m_configItems.sortType; }
[[nodiscard]] bool isSortReverse() const { return m_configItems.sortReverse; }
static const QString s_DefaultConfigFileName; static const QString s_DefaultConfigFileName;
private: private:
@@ -53,8 +64,10 @@ class Config : public QObject {
double styleAspectRatio = 1.6; double styleAspectRatio = 1.6;
int styleImageWidth = 320; int styleImageWidth = 320;
int styleImageFocusWidth = 480; int styleImageFocusWidth = 480;
int styleWindowWidth = 800; int styleWindowWidth = 720;
int styleWindowHeight = 600; int styleWindowHeight = 500;
SortType sortType = SortType::None;
bool sortReverse = false;
} m_configItems; } m_configItems;
QStringList m_wallpapers; QStringList m_wallpapers;
+32
View File
@@ -36,6 +36,38 @@
</property> </property>
<widget class="QWidget" name="centralwidget"> <widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="mainLayout"> <layout class="QVBoxLayout" name="mainLayout">
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="topLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</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">
+64 -17
View File
@@ -1,20 +1,20 @@
/* /*
* @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 20:06:23 * @LastEditTime: 2025-08-06 00:47:21
* @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"
#include <pthread.h> #include <pthread.h>
#include <qevent.h> #include <qboxlayout.h>
#include <qpropertyanimation.h>
#include <qvariantanimation.h>
#include <QLabel> #include <QLabel>
#include <QMetaObject> #include <QMetaObject>
#include <QScrollArea> #include <QScrollArea>
#include <QScrollBar> #include <QScrollBar>
#include <QVector>
#include <functional>
#include "logger.h" #include "logger.h"
#include "ui_images_carousel.h" #include "ui_images_carousel.h"
@@ -24,6 +24,8 @@ using namespace GeneralLogger;
ImagesCarousel::ImagesCarousel(const double itemAspectRatio, ImagesCarousel::ImagesCarousel(const double itemAspectRatio,
const int itemWidth, const int itemWidth,
const int itemFocusWidth, const int itemFocusWidth,
const Config::SortType sortType,
const bool sortReverse,
QWidget* parent) QWidget* parent)
: QWidget(parent), : QWidget(parent),
ui(new Ui::ImagesCarousel), ui(new Ui::ImagesCarousel),
@@ -32,11 +34,18 @@ ImagesCarousel::ImagesCarousel(const double itemAspectRatio,
m_itemWidth(itemWidth), m_itemWidth(itemWidth),
m_itemHeight(static_cast<int>(itemWidth / itemAspectRatio)), m_itemHeight(static_cast<int>(itemWidth / itemAspectRatio)),
m_itemFocusWidth(itemFocusWidth), m_itemFocusWidth(itemFocusWidth),
m_itemFocusHeight(static_cast<int>(itemFocusWidth / itemAspectRatio)) { m_itemFocusHeight(static_cast<int>(itemFocusWidth / itemAspectRatio)),
m_sortType(sortType),
m_sortReverse(sortReverse) {
ui->setupUi(this); ui->setupUi(this);
m_imagesLayout = dynamic_cast<QHBoxLayout*>(ui->scrollAreaWidgetContents->layout());
connect(m_updateTimer, &QTimer::timeout, this, &ImagesCarousel::_updateImages); connect(m_updateTimer, &QTimer::timeout, this, &ImagesCarousel::_updateImages);
m_updateTimer->start(100); m_updateTimer->start(100);
connect(this, &ImagesCarousel::imagesLoaded, this, [this]() {
_focusCurrImage();
});
} }
ImagesCarousel::~ImagesCarousel() { ImagesCarousel::~ImagesCarousel() {
@@ -44,15 +53,23 @@ ImagesCarousel::~ImagesCarousel() {
for (auto item : std::as_const(m_imageQueue)) { for (auto item : std::as_const(m_imageQueue)) {
delete item; delete item;
} }
// memory of items in m_loadedImages managed by Qt parent-child system
// ...
if (m_scrollAnimation) { if (m_scrollAnimation) {
m_scrollAnimation->stop(); m_scrollAnimation->stop();
delete m_scrollAnimation; delete m_scrollAnimation;
} }
} }
void ImagesCarousel::appendImage(const QString& path) { void ImagesCarousel::appendImages(const QStringList& paths) {
ImageLoader* loader = new ImageLoader(path, this); {
QThreadPool::globalInstance()->start(loader); QMutexLocker locker(&m_imageCountMutex);
m_imageCount += paths.size();
}
for (const QString& path : paths) {
ImageLoader* loader = new ImageLoader(path, this);
QThreadPool::globalInstance()->start(loader);
}
} }
ImageLoader::ImageLoader(const QString& path, ImagesCarousel* carousel) ImageLoader::ImageLoader(const QString& path, ImagesCarousel* carousel)
@@ -78,19 +95,45 @@ void ImagesCarousel::_addImageToQueue(const ImageData* data) {
void ImagesCarousel::_updateImages() { void ImagesCarousel::_updateImages() {
QMutexLocker locker(&m_queueMutex); QMutexLocker locker(&m_queueMutex);
static const QVector<std::function<bool(const ImageItem*, const ImageItem*)>> cmpFuncs = {
[](auto, auto) {
return false;
}, // None
[](auto a, auto b) {
return a->getFileName() < b->getFileName();
},
[](auto a, auto b) {
return a->getFileDate() < b->getFileDate();
},
[](auto a, auto b) {
return a->getFileSize() < b->getFileSize();
},
};
int processCount = 0; int processCount = 0;
while (!m_imageQueue.isEmpty() && processCount < 5) { while (!m_imageQueue.isEmpty() && processCount < 5) {
ImageItem* item = m_imageQueue.dequeue(); ImageItem* item = m_imageQueue.dequeue();
ui->scrollAreaWidgetContents->layout()->addWidget(item);
m_loadedImages.append(item); // insert into correct position based on sort type and direction
// focus first image // currently O(n^2), but better as O(n * (n + log(n))) with vector and binary search
if (m_loadedImages.size() == 1) { qint64 inserPos = m_loadedImages.size();
item->setFocus(true); if (m_sortType != Config::SortType::None) {
} else { for (auto it = m_loadedImages.rbegin();
item->setFocus(false); it != m_loadedImages.rend() &&
cmpFuncs[static_cast<int>(m_sortType)](*it, item) == m_sortReverse;
++it, --inserPos);
} }
m_loadedImages.insert(inserPos, item);
m_imagesLayout->insertWidget(inserPos, item);
processCount++; processCount++;
} }
{
QMutexLocker countLocker(&m_imageCountMutex);
if (m_loadedImages.size() >= m_imageCount) {
emit imagesLoaded();
}
}
} }
void ImageLoader::run() { void ImageLoader::run() {
@@ -101,8 +144,7 @@ void ImageLoader::run() {
Q_ARG(const ImageData*, data)); Q_ARG(const ImageData*, data));
} }
ImageData::ImageData(const QString& p, const int initWidth, const int initHeight) : path(p) { ImageData::ImageData(const QString& p, const int initWidth, const int initHeight) : file(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));
} }
@@ -137,11 +179,16 @@ void ImagesCarousel::focusPrevImage() {
} }
void ImagesCarousel::_unfocusCurrImage() { void ImagesCarousel::_unfocusCurrImage() {
// bound check was (or should) done by caller
m_loadedImages[m_currentIndex]->setFocus(false); m_loadedImages[m_currentIndex]->setFocus(false);
} }
void ImagesCarousel::_focusCurrImage() { void ImagesCarousel::_focusCurrImage() {
// bound check was (or should) done by caller
m_loadedImages[m_currentIndex]->setFocus(true); m_loadedImages[m_currentIndex]->setFocus(true);
emit imageFocused(m_loadedImages[m_currentIndex]->getFileFullPath(),
m_currentIndex,
m_loadedImages.size());
auto hScrollBar = ui->scrollArea->horizontalScrollBar(); auto hScrollBar = ui->scrollArea->horizontalScrollBar();
int spacing = ui->scrollAreaWidgetContents->layout()->spacing(); int spacing = ui->scrollAreaWidgetContents->layout()->spacing();
int centerOffset = (m_itemWidth + spacing) * m_currentIndex + m_itemFocusWidth / 2 - spacing; int centerOffset = (m_itemWidth + spacing) * m_currentIndex + m_itemFocusWidth / 2 - spacing;
+30 -11
View File
@@ -1,14 +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 19:47:43 * @LastEditTime: 2025-08-06 00:47:12
* @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 <QFileInfo>
#include <QHBoxLayout>
#include <QKeyEvent> #include <QKeyEvent>
#include <QLabel> #include <QLabel>
#include <QMutex> #include <QMutex>
@@ -22,6 +22,8 @@
#include <QTimer> #include <QTimer>
#include <QWidget> #include <QWidget>
#include "config.h"
class ImagesCarousel; class ImagesCarousel;
/** /**
@@ -29,7 +31,7 @@ class ImagesCarousel;
* and can be safely created and passed between threads. * and can be safely created and passed between threads.
*/ */
struct ImageData { struct ImageData {
QString path; QFileInfo file;
QPixmap pixmap; QPixmap pixmap;
explicit ImageData(const QString& p, const int initWidth, const int initHeight); explicit ImageData(const QString& p, const int initWidth, const int initHeight);
@@ -51,10 +53,16 @@ class ImageItem : public QLabel {
~ImageItem() override; ~ImageItem() override;
[[nodiscard]] const QString& getPath() const { return m_data->path; } [[nodiscard]] QString getFileFullPath() const { return m_data->file.absoluteFilePath(); }
[[nodiscard]] QString getFileName() const { return m_data->file.fileName(); }
[[nodiscard]] QDateTime getFileDate() const { return m_data->file.lastModified(); }
[[nodiscard]] const QPixmap& getPixmap() const { return m_data->pixmap; } [[nodiscard]] const QPixmap& getPixmap() const { return m_data->pixmap; }
[[nodiscard]] qint64 getFileSize() const { return m_data->file.size(); }
public: public:
void setFocus(bool focus = true); void setFocus(bool focus = true);
@@ -90,6 +98,8 @@ class ImagesCarousel : public QWidget {
explicit ImagesCarousel(const double itemAspectRatio, explicit ImagesCarousel(const double itemAspectRatio,
const int itemWidth, const int itemWidth,
const int itemFocusWidth, const int itemFocusWidth,
const Config::SortType sortType,
const bool sortReverse,
QWidget* parent = nullptr); QWidget* parent = nullptr);
~ImagesCarousel(); ~ImagesCarousel();
@@ -99,13 +109,15 @@ class ImagesCarousel : public QWidget {
if (m_currentIndex < 0 || m_currentIndex >= m_loadedImages.size()) { if (m_currentIndex < 0 || m_currentIndex >= m_loadedImages.size()) {
return ""; return "";
} }
return m_loadedImages[m_currentIndex]->getPath(); return m_loadedImages[m_currentIndex]->getFileFullPath();
} }
const int m_itemWidth = 320; const int m_itemWidth = 320;
const int m_itemHeight = 180; const int m_itemHeight = 180;
const int m_itemFocusWidth = 480; const int m_itemFocusWidth = 480;
const int m_itemFocusHeight = 270; const int m_itemFocusHeight = 270;
const Config::SortType m_sortType = Config::SortType::None;
const bool m_sortReverse = false;
public slots: public slots:
void focusNextImage(); void focusNextImage();
@@ -115,7 +127,7 @@ class ImagesCarousel : public QWidget {
void _unfocusCurrImage(); void _unfocusCurrImage();
public: public:
void appendImage(const QString& path); void appendImages(const QStringList& paths);
private: private:
Q_INVOKABLE void _addImageToQueue(const ImageData* data); Q_INVOKABLE void _addImageToQueue(const ImageData* data);
@@ -130,6 +142,13 @@ class ImagesCarousel : public QWidget {
QTimer* m_updateTimer; QTimer* m_updateTimer;
int m_currentIndex = 0; int m_currentIndex = 0;
QPropertyAnimation* m_scrollAnimation; QPropertyAnimation* m_scrollAnimation;
QHBoxLayout* m_imagesLayout = nullptr;
QMutex m_imageCountMutex;
int m_imageCount = 0;
signals:
void imageFocused(const QString& path, const int index, const int count);
void imagesLoaded();
}; };
class ImagesCarouselScrollArea : public QScrollArea { class ImagesCarouselScrollArea : public QScrollArea {
+15 -5
View File
@@ -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 20:12:40 * @LastEditTime: 2025-08-06 00:48:11
* @Description: MainWindow implementation. * @Description: MainWindow implementation.
*/ */
#include "main_window.h" #include "main_window.h"
@@ -17,6 +17,11 @@
using namespace GeneralLogger; using namespace GeneralLogger;
static QString splitNameFromPath(const QString &path) {
QFileInfo fileInfo(path);
return fileInfo.fileName();
}
MainWindow::MainWindow(const Config &config, QWidget *parent) MainWindow::MainWindow(const Config &config, QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow), m_config(config) { : QMainWindow(parent), ui(new Ui::MainWindow), m_config(config) {
ui->setupUi(this); ui->setupUi(this);
@@ -33,8 +38,11 @@ void MainWindow::_setupUI() {
m_config.getStyleAspectRatio(), m_config.getStyleAspectRatio(),
m_config.getStyleImageWidth(), m_config.getStyleImageWidth(),
m_config.getStyleImageFocusWidth(), m_config.getStyleImageFocusWidth(),
m_config.getSortType(),
m_config.isSortReverse(),
this); this);
ui->mainLayout->insertWidget(0, m_carousel); ui->mainLayout->insertWidget(2, m_carousel);
connect(m_carousel, &ImagesCarousel::imageFocused, this, &MainWindow::_onImageFocused);
// set window size // set window size
setMinimumSize(m_config.getStyleWindowWidth(), m_config.getStyleWindowHeight()); setMinimumSize(m_config.getStyleWindowWidth(), m_config.getStyleWindowHeight());
@@ -45,9 +53,7 @@ void MainWindow::_setupUI() {
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()) { m_carousel->appendImages(m_config.getWallpapers());
m_carousel->appendImage(image);
}
} }
void MainWindow::keyPressEvent(QKeyEvent *event) { void MainWindow::keyPressEvent(QKeyEvent *event) {
@@ -90,4 +96,8 @@ void MainWindow::onConfirm() {
void MainWindow::onCancel() { void MainWindow::onCancel() {
close(); close();
}
void MainWindow::_onImageFocused(const QString &path, const int index, const int count) {
ui->topLabel->setText(QString("%1 (%2/%3)").arg(splitNameFromPath(path)).arg(index + 1).arg(count));
} }
+4 -1
View File
@@ -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 19:53:51 * @LastEditTime: 2025-08-06 00:47:04
* @Description: MainWindow implementation. * @Description: MainWindow implementation.
*/ */
#ifndef MAINWINDOW_H #ifndef MAINWINDOW_H
@@ -37,6 +37,9 @@ class MainWindow : public QMainWindow {
private: private:
void _setupUI(); void _setupUI();
private slots:
void _onImageFocused(const QString &path, const int index, const int count);
private: private:
Ui::MainWindow *ui; Ui::MainWindow *ui;
ImagesCarousel *m_carousel = nullptr; ImagesCarousel *m_carousel = nullptr;