wip: working carousel :D
This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"wallpaper": {
|
||||||
|
"paths": [
|
||||||
|
"~/Pictures/116327446_p0.jpg"
|
||||||
|
],
|
||||||
|
"dirs": [
|
||||||
|
"~/.config/backgrounds"
|
||||||
|
],
|
||||||
|
"excludes": [
|
||||||
|
"~/.config/backgrounds/nao-start-crop-adjusted.jpg",
|
||||||
|
"~/.config/backgrounds/README.md"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"confirm": "~/.scripts/change_wallpaper.sh <path> <color>"
|
||||||
|
}
|
||||||
|
}
|
||||||
+198
-2
@@ -1,12 +1,208 @@
|
|||||||
|
/*
|
||||||
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
|
* @Date: 2025-08-05 01:22:53
|
||||||
|
* @LastEditTime: 2025-08-05 16:51:12
|
||||||
|
* @Description:
|
||||||
|
*/
|
||||||
#include "images_carousel.h"
|
#include "images_carousel.h"
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <qevent.h>
|
||||||
|
#include <qpropertyanimation.h>
|
||||||
|
#include <qvariantanimation.h>
|
||||||
|
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QMetaObject>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QScrollBar>
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
#include "ui_images_carousel.h"
|
#include "ui_images_carousel.h"
|
||||||
|
|
||||||
ImagesCarousel::ImagesCarousel(QWidget *parent) : QWidget(parent),
|
using namespace GeneralLogger;
|
||||||
ui(new Ui::ImagesCarousel) {
|
|
||||||
|
ImagesCarousel::ImagesCarousel(QWidget* parent)
|
||||||
|
: QWidget(parent),
|
||||||
|
ui(new Ui::ImagesCarousel),
|
||||||
|
m_updateTimer(new QTimer(this)),
|
||||||
|
m_scrollAnimation(nullptr) {
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
connect(m_updateTimer, &QTimer::timeout, this, &ImagesCarousel::updateImages);
|
||||||
|
m_updateTimer->start(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImagesCarousel::~ImagesCarousel() {
|
ImagesCarousel::~ImagesCarousel() {
|
||||||
delete ui;
|
delete ui;
|
||||||
|
for (auto item : m_imageQueue) {
|
||||||
|
delete item;
|
||||||
|
}
|
||||||
|
if (m_scrollAnimation) {
|
||||||
|
m_scrollAnimation->stop();
|
||||||
|
delete m_scrollAnimation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImagesCarousel::appendImage(const QString& path) {
|
||||||
|
ImageLoader* loader = new ImageLoader(path, this);
|
||||||
|
QThreadPool::globalInstance()->start(loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageLoader::ImageLoader(const QString& path, ImagesCarousel* carousel)
|
||||||
|
: m_path(path), m_carousel(carousel) {
|
||||||
|
setAutoDelete(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImagesCarousel::addImageToQueue(const ImageData* data) {
|
||||||
|
QMutexLocker locker(&m_queueMutex);
|
||||||
|
auto imageItem = new ImageItem(data,
|
||||||
|
s_itemWidth,
|
||||||
|
s_itemHeight,
|
||||||
|
s_itemFocusWidth,
|
||||||
|
s_itemFocusHeight,
|
||||||
|
this);
|
||||||
|
m_imageQueue.enqueue(imageItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImagesCarousel::updateImages() {
|
||||||
|
QMutexLocker locker(&m_queueMutex);
|
||||||
|
|
||||||
|
int processCount = 0;
|
||||||
|
while (!m_imageQueue.isEmpty() && processCount < 5) {
|
||||||
|
ImageItem* item = m_imageQueue.dequeue();
|
||||||
|
ui->scrollAreaWidgetContents->layout()->addWidget(item);
|
||||||
|
m_loadedImages.append(item);
|
||||||
|
if (m_loadedImages.size() == 1) {
|
||||||
|
item->focusImage();
|
||||||
|
} else {
|
||||||
|
item->unfocusImage();
|
||||||
|
}
|
||||||
|
processCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageLoader::run() {
|
||||||
|
auto data = new ImageData(m_path);
|
||||||
|
QMetaObject::invokeMethod(m_carousel,
|
||||||
|
"addImageToQueue",
|
||||||
|
Qt::QueuedConnection,
|
||||||
|
Q_ARG(const ImageData*, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageData::ImageData(const QString& p) : path(p) {
|
||||||
|
path = p;
|
||||||
|
if (!pixmap.load(p)) {
|
||||||
|
warn(QString("Failed to load image from path: %1").arg(p));
|
||||||
|
}
|
||||||
|
// resize in "cover" mode
|
||||||
|
const QSize targetSize(ImagesCarousel::s_itemWidth, ImagesCarousel::s_itemHeight);
|
||||||
|
pixmap = pixmap.scaled(targetSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
||||||
|
|
||||||
|
// Crop to center
|
||||||
|
int x = (pixmap.width() - targetSize.width()) / 2;
|
||||||
|
int y = (pixmap.height() - targetSize.height()) / 2;
|
||||||
|
pixmap = pixmap.copy(x, y, targetSize.width(), targetSize.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImagesCarousel::focusNextImage() {
|
||||||
|
unfocusCurrImage();
|
||||||
|
if (m_loadedImages.size() <= 1) return;
|
||||||
|
m_currentIndex++;
|
||||||
|
if (m_currentIndex >= m_loadedImages.size()) {
|
||||||
|
m_currentIndex = 0;
|
||||||
|
}
|
||||||
|
focusCurrImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImagesCarousel::focusPrevImage() {
|
||||||
|
if (m_loadedImages.size() <= 1) return;
|
||||||
|
unfocusCurrImage();
|
||||||
|
m_currentIndex--;
|
||||||
|
if (m_currentIndex < 0) {
|
||||||
|
m_currentIndex = m_loadedImages.size() - 1;
|
||||||
|
}
|
||||||
|
focusCurrImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImagesCarousel::unfocusCurrImage() {
|
||||||
|
m_loadedImages[m_currentIndex]->unfocusImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImagesCarousel::focusCurrImage() {
|
||||||
|
m_loadedImages[m_currentIndex]->focusImage();
|
||||||
|
auto hScrollBar = ui->scrollArea->horizontalScrollBar();
|
||||||
|
int spacing = ui->scrollAreaWidgetContents->layout()->spacing();
|
||||||
|
int centerOffset = (s_itemWidth + spacing) * m_currentIndex + s_itemFocusWidth / 2 - spacing;
|
||||||
|
int leftOffset = centerOffset - ui->scrollArea->width() / 2;
|
||||||
|
if (leftOffset < 0) {
|
||||||
|
leftOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_scrollAnimation) {
|
||||||
|
m_scrollAnimation->stop();
|
||||||
|
delete m_scrollAnimation;
|
||||||
|
m_scrollAnimation = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_scrollAnimation = new QPropertyAnimation(hScrollBar, "value");
|
||||||
|
m_scrollAnimation->setDuration(300);
|
||||||
|
m_scrollAnimation->setStartValue(hScrollBar->value());
|
||||||
|
m_scrollAnimation->setEndValue(leftOffset);
|
||||||
|
m_scrollAnimation->setEasingCurve(QEasingCurve::OutCubic);
|
||||||
|
m_scrollAnimation->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageItem::ImageItem(const ImageData* data,
|
||||||
|
const int itemWidth,
|
||||||
|
const int itemHeight,
|
||||||
|
const int itemFocusWidth,
|
||||||
|
const int itemFocusHeight,
|
||||||
|
QWidget* parent)
|
||||||
|
: QLabel(parent),
|
||||||
|
m_data(data),
|
||||||
|
m_itemSize(itemWidth, itemHeight),
|
||||||
|
m_itemFocusSize(itemFocusWidth, itemFocusHeight) {
|
||||||
|
setPixmap(data->pixmap);
|
||||||
|
setFixedSize(ImagesCarousel::s_itemWidth, ImagesCarousel::s_itemHeight);
|
||||||
|
setScaledContents(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageItem::focusImage() {
|
||||||
|
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_itemFocusSize);
|
||||||
|
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);
|
||||||
|
connect(m_scaleAnimation,
|
||||||
|
&QPropertyAnimation::valueChanged,
|
||||||
|
this,
|
||||||
|
[this](const QVariant& value) {
|
||||||
|
setFixedSize(value.toSize());
|
||||||
|
});
|
||||||
|
m_scaleAnimation->start();
|
||||||
}
|
}
|
||||||
+104
-2
@@ -1,8 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
|
* @Date: 2025-08-05 01:22:53
|
||||||
|
* @LastEditTime: 2025-08-05 16:49:22
|
||||||
|
* @Description:
|
||||||
|
*/
|
||||||
#ifndef IMAGES_CAROUSEL_H
|
#ifndef IMAGES_CAROUSEL_H
|
||||||
#define IMAGES_CAROUSEL_H
|
#define IMAGES_CAROUSEL_H
|
||||||
|
|
||||||
|
#include <QKeyEvent>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QPropertyAnimation>
|
||||||
|
#include <QQueue>
|
||||||
|
#include <QRunnable>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QThreadPool>
|
||||||
|
#include <QTimer>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
class ImagesCarousel;
|
||||||
|
|
||||||
|
struct ImageData {
|
||||||
|
QString path;
|
||||||
|
QPixmap pixmap;
|
||||||
|
|
||||||
|
ImageData() = default;
|
||||||
|
|
||||||
|
explicit ImageData(const QString& p);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImageItem : public QLabel {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ImageItem(const ImageData* data,
|
||||||
|
const int itemWidth,
|
||||||
|
const int itemHeight,
|
||||||
|
const int itemFocusWidth,
|
||||||
|
const int itemFocusHeight,
|
||||||
|
QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void focusImage();
|
||||||
|
void unfocusImage();
|
||||||
|
|
||||||
|
private:
|
||||||
|
const ImageData* m_data;
|
||||||
|
QSize m_itemSize;
|
||||||
|
QSize m_itemFocusSize;
|
||||||
|
QPropertyAnimation* m_scaleAnimation = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImageLoader : public QRunnable {
|
||||||
|
public:
|
||||||
|
ImageLoader(const QString& path, ImagesCarousel* carousel);
|
||||||
|
void run() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_path;
|
||||||
|
ImagesCarousel* m_carousel;
|
||||||
|
};
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class ImagesCarousel;
|
class ImagesCarousel;
|
||||||
}
|
}
|
||||||
@@ -11,11 +71,53 @@ class ImagesCarousel : public QWidget {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ImagesCarousel(QWidget *parent = nullptr);
|
explicit ImagesCarousel(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;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void addImageToQueue(const ImageData* data);
|
||||||
|
void appendImage(const QString& path);
|
||||||
|
void focusNextImage();
|
||||||
|
void focusPrevImage();
|
||||||
|
void unfocusCurrImage();
|
||||||
|
void focusCurrImage();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void updateImages();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::ImagesCarousel *ui;
|
Ui::ImagesCarousel* ui;
|
||||||
|
QMutex m_queueMutex;
|
||||||
|
QQueue<ImageItem*> m_imageQueue;
|
||||||
|
QList<ImageItem*> m_loadedImages;
|
||||||
|
QTimer* m_updateTimer;
|
||||||
|
int m_currentIndex = 0;
|
||||||
|
QPropertyAnimation* m_scrollAnimation;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void imageLoaded(ImageData* imageData);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImagesCarouselScrollArea : public QScrollArea {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ImagesCarouselScrollArea(QWidget* parent = nullptr)
|
||||||
|
: QScrollArea(parent) {
|
||||||
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
|
setWidgetResizable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void keyPressEvent(QKeyEvent* event) override {
|
||||||
|
event->ignore();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // IMAGES_CAROUSEL_H
|
#endif // IMAGES_CAROUSEL_H
|
||||||
|
|||||||
@@ -13,7 +13,41 @@
|
|||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="ImagesCarouselScrollArea" name="scrollArea">
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">border: none</string>
|
||||||
|
</property>
|
||||||
|
<property name="verticalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>382</width>
|
||||||
|
<height>282</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>ImagesCarouselScrollArea</class>
|
||||||
|
<extends>QScrollArea</extends>
|
||||||
|
<header>images_carousel.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
|||||||
+9
-1
@@ -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 12:18:00
|
* @LastEditTime: 2025-08-05 16:51:52
|
||||||
* @Description:
|
* @Description:
|
||||||
*/
|
*/
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
@@ -30,6 +30,10 @@ void MainWindow::_setupUI() {
|
|||||||
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()) {
|
||||||
|
ui->carousel->appendImage(image);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::keyPressEvent(QKeyEvent *event) {
|
void MainWindow::keyPressEvent(QKeyEvent *event) {
|
||||||
@@ -37,6 +41,10 @@ void MainWindow::keyPressEvent(QKeyEvent *event) {
|
|||||||
onCancel();
|
onCancel();
|
||||||
} 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) {
|
||||||
|
ui->carousel->focusNextImage();
|
||||||
|
} else if (event->key() == Qt::Key_Left) {
|
||||||
|
ui->carousel->focusPrevImage();
|
||||||
} else {
|
} else {
|
||||||
QMainWindow::keyPressEvent(event);
|
QMainWindow::keyPressEvent(event);
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -69,12 +69,12 @@
|
|||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="cancelButton">
|
<widget class="QPushButton" name="confirmButton">
|
||||||
<property name="styleSheet">
|
<property name="styleSheet">
|
||||||
<string notr="true">color: #f38ba8</string>
|
<string notr="true">color: #a6e3a1</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Cancel</string>
|
<string>Confirm</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="flat">
|
<property name="flat">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@@ -82,12 +82,12 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="confirmButton">
|
<widget class="QPushButton" name="cancelButton">
|
||||||
<property name="styleSheet">
|
<property name="styleSheet">
|
||||||
<string notr="true">color: #a6e3a1</string>
|
<string notr="true">color: #f38ba8</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Confirm</string>
|
<string>Cancel</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="flat">
|
<property name="flat">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
|||||||
Reference in New Issue
Block a user