forked from quickshell/quickshell
Compare commits
No commits in common. "color-quantization" and "master" have entirely different histories.
color-quan
...
master
9 changed files with 30 additions and 558 deletions
139
CONTRIBUTING.md
139
CONTRIBUTING.md
|
@ -30,139 +30,6 @@ If the results look stupid, fix the clang-format file if possible,
|
||||||
or disable clang-format in the affected area
|
or disable clang-format in the affected area
|
||||||
using `// clang-format off` and `// clang-format on`.
|
using `// clang-format off` and `// clang-format on`.
|
||||||
|
|
||||||
#### Style preferences not caught by clang-format
|
|
||||||
These are flexible. You can ignore them if it looks or works better to
|
|
||||||
for one reason or another.
|
|
||||||
|
|
||||||
Use `auto` if the type of a variable can be deduced automatically, instead of
|
|
||||||
redeclaring the returned value's type. Additionally, auto should be used when a
|
|
||||||
constructor takes arguments.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
auto x = <expr>; // ok
|
|
||||||
auto x = QString::number(3); // ok
|
|
||||||
QString x; // ok
|
|
||||||
QString x = "foo"; // ok
|
|
||||||
auto x = QString("foo"); // ok
|
|
||||||
|
|
||||||
auto x = QString(); // avoid
|
|
||||||
QString x(); // avoid
|
|
||||||
QString x("foo"); // avoid
|
|
||||||
```
|
|
||||||
|
|
||||||
Put newlines around logical units of code, and after closing braces. If the
|
|
||||||
most reasonable logical unit of code takes only a single line, it should be
|
|
||||||
merged into the next single line logical unit if applicable.
|
|
||||||
```cpp
|
|
||||||
// multiple units
|
|
||||||
auto x = <expr>; // unit 1
|
|
||||||
auto y = <expr>; // unit 2
|
|
||||||
|
|
||||||
auto x = <expr>; // unit 1
|
|
||||||
emit this->y(); // unit 2
|
|
||||||
|
|
||||||
auto x1 = <expr>; // unit 1
|
|
||||||
auto x2 = <expr>; // unit 1
|
|
||||||
auto x3 = <expr>; // unit 1
|
|
||||||
|
|
||||||
auto y1 = <expr>; // unit 2
|
|
||||||
auto y2 = <expr>; // unit 2
|
|
||||||
auto y3 = <expr>; // unit 2
|
|
||||||
|
|
||||||
// one unit
|
|
||||||
auto x = <expr>;
|
|
||||||
if (x...) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
// if more than one variable needs to be used then add a newline
|
|
||||||
auto x = <expr>;
|
|
||||||
auto y = <expr>;
|
|
||||||
|
|
||||||
if (x && y) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Class formatting:
|
|
||||||
```cpp
|
|
||||||
//! Doc comment summary
|
|
||||||
/// Doc comment body
|
|
||||||
class Foo: public QObject {
|
|
||||||
// The Q_OBJECT macro comes first. Macros are ; terminated.
|
|
||||||
Q_OBJECT;
|
|
||||||
QML_ELEMENT;
|
|
||||||
QML_CLASSINFO(...);
|
|
||||||
// Properties must stay on a single line or the doc generator won't be able to pick them up
|
|
||||||
Q_PROPERTY(...);
|
|
||||||
/// Doc comment
|
|
||||||
Q_PROPERTY(...);
|
|
||||||
/// Doc comment
|
|
||||||
Q_PROPERTY(...);
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Classes should have explicit constructors if they aren't intended to
|
|
||||||
// implicitly cast. The constructor can be inline in the header if it has no body.
|
|
||||||
explicit Foo(QObject* parent = nullptr): QObject(parent) {}
|
|
||||||
|
|
||||||
// Instance functions if applicable.
|
|
||||||
static Foo* instance();
|
|
||||||
|
|
||||||
// Member functions unrelated to properties come next
|
|
||||||
void function();
|
|
||||||
void function();
|
|
||||||
void function();
|
|
||||||
|
|
||||||
// Then Q_INVOKABLEs
|
|
||||||
Q_INVOKABLE function();
|
|
||||||
/// Doc comment
|
|
||||||
Q_INVOKABLE function();
|
|
||||||
/// Doc comment
|
|
||||||
Q_INVOKABLE function();
|
|
||||||
|
|
||||||
// Then property related functions, in the order (bindable, getter, setter).
|
|
||||||
// Related functions may be included here as well. Function bodies may be inline
|
|
||||||
// if they are a single expression. There should be a newline between each
|
|
||||||
// property's methods.
|
|
||||||
[[nodiscard]] QBindable<T> bindableFoo() { return &this->bFoo; }
|
|
||||||
[[nodiscard]] T foo() const { return this->foo; }
|
|
||||||
void setFoo();
|
|
||||||
|
|
||||||
[[nodiscard]] T bar() const { return this->foo; }
|
|
||||||
void setBar();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
// Signals that are not property change related go first.
|
|
||||||
// Property change signals go in property definition order.
|
|
||||||
void asd();
|
|
||||||
void asd2();
|
|
||||||
void fooChanged();
|
|
||||||
void barChanged();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
// generally Q_INVOKABLEs are preferred to public slots.
|
|
||||||
void slot();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
// ...
|
|
||||||
|
|
||||||
private:
|
|
||||||
// statics, then functions, then fields
|
|
||||||
static const foo BAR;
|
|
||||||
static void foo();
|
|
||||||
|
|
||||||
void foo();
|
|
||||||
void bar();
|
|
||||||
|
|
||||||
// property related members are prefixed with `m`.
|
|
||||||
QString mFoo;
|
|
||||||
QString bar;
|
|
||||||
|
|
||||||
// Bindables go last and should be prefixed with `b`.
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Foo, QString, bFoo, &Foo::fooChanged);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Linter
|
### Linter
|
||||||
All contributions should pass the linter.
|
All contributions should pass the linter.
|
||||||
|
|
||||||
|
@ -170,11 +37,11 @@ Note that running the linter requires disabling precompiled
|
||||||
headers and including the test codepaths:
|
headers and including the test codepaths:
|
||||||
```sh
|
```sh
|
||||||
$ just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON
|
$ just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON
|
||||||
$ just lint-changed
|
$ just lint
|
||||||
```
|
```
|
||||||
|
|
||||||
If the linter is complaining about something that you think it should not,
|
If the linter is complaining about something that you think it should not,
|
||||||
please disable the lint in your MR and explain your reasoning if it isn't obvious.
|
please disable the lint in your MR and explain your reasoning.
|
||||||
|
|
||||||
### Tests
|
### Tests
|
||||||
If you feel like the feature you are working on is very complex or likely to break,
|
If you feel like the feature you are working on is very complex or likely to break,
|
||||||
|
@ -210,7 +77,7 @@ and list of headers to scan for documentation.
|
||||||
### Commits
|
### Commits
|
||||||
Please structure your commit messages as `scope[!]: commit` where
|
Please structure your commit messages as `scope[!]: commit` where
|
||||||
the scope is something like `core` or `service/mpris`. (pick what has been
|
the scope is something like `core` or `service/mpris`. (pick what has been
|
||||||
used historically or what makes sense if new). Add `!` for changes that break
|
used historically or what makes sense if new.) Add `!` for changes that break
|
||||||
existing APIs or functionality.
|
existing APIs or functionality.
|
||||||
|
|
||||||
Commit descriptions should contain a summary of the changes if they are not
|
Commit descriptions should contain a summary of the changes if they are not
|
||||||
|
|
|
@ -37,7 +37,6 @@ qt_add_library(quickshell-core STATIC
|
||||||
common.cpp
|
common.cpp
|
||||||
iconprovider.cpp
|
iconprovider.cpp
|
||||||
scriptmodel.cpp
|
scriptmodel.cpp
|
||||||
colorquantizer.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_qml_module(quickshell-core
|
qt_add_qml_module(quickshell-core
|
||||||
|
|
|
@ -1,230 +0,0 @@
|
||||||
#include "colorquantizer.hpp"
|
|
||||||
|
|
||||||
#include <qcolor.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qloggingcategory.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qqmllist.h>
|
|
||||||
#include <qthreadpool.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
Q_LOGGING_CATEGORY(logColorQuantizer, "quickshell.colorquantizer", QtWarningMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
ColorQuantizerOperation::ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize)
|
|
||||||
: source(source)
|
|
||||||
, maxDepth(depth)
|
|
||||||
, rescaleSize(rescaleSize) {
|
|
||||||
setAutoDelete(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCancel) {
|
|
||||||
if (shouldCancel.loadAcquire() || source->isEmpty()) return;
|
|
||||||
|
|
||||||
colors.clear();
|
|
||||||
|
|
||||||
auto image = QImage(source->toLocalFile());
|
|
||||||
if ((image.width() > rescaleSize || image.height() > rescaleSize) && rescaleSize > 0) {
|
|
||||||
image = image.scaled(
|
|
||||||
static_cast<int>(rescaleSize),
|
|
||||||
static_cast<int>(rescaleSize),
|
|
||||||
Qt::KeepAspectRatio,
|
|
||||||
Qt::SmoothTransformation
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (image.isNull()) {
|
|
||||||
qCWarning(logColorQuantizer) << "Failed to load image from" << source;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QColor> pixels;
|
|
||||||
for (int y = 0; y != image.height(); ++y) {
|
|
||||||
for (int x = 0; x != image.width(); ++x) {
|
|
||||||
auto pixel = image.pixel(x, y);
|
|
||||||
if (qAlpha(pixel) == 0) continue;
|
|
||||||
|
|
||||||
pixels.append(QColor::fromRgb(pixel));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto startTime = QDateTime::currentDateTime();
|
|
||||||
|
|
||||||
colors = quantization(pixels, 0);
|
|
||||||
|
|
||||||
auto endTime = QDateTime::currentDateTime();
|
|
||||||
auto milliseconds = startTime.msecsTo(endTime);
|
|
||||||
qCDebug(logColorQuantizer) << "Color Quantization took: " << milliseconds << "ms";
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QColor> ColorQuantizerOperation::quantization(
|
|
||||||
QList<QColor>& rgbValues,
|
|
||||||
qreal depth,
|
|
||||||
const QAtomicInteger<bool>& shouldCancel
|
|
||||||
) {
|
|
||||||
if (shouldCancel.loadAcquire()) return QList<QColor>();
|
|
||||||
|
|
||||||
if (depth >= maxDepth || rgbValues.isEmpty()) {
|
|
||||||
if (rgbValues.isEmpty()) return QList<QColor>();
|
|
||||||
|
|
||||||
auto totalR = 0;
|
|
||||||
auto totalG = 0;
|
|
||||||
auto totalB = 0;
|
|
||||||
|
|
||||||
for (const auto& color: rgbValues) {
|
|
||||||
if (shouldCancel.loadAcquire()) return QList<QColor>();
|
|
||||||
|
|
||||||
totalR += color.red();
|
|
||||||
totalG += color.green();
|
|
||||||
totalB += color.blue();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto avgColor = QColor(
|
|
||||||
qRound(totalR / static_cast<double>(rgbValues.size())),
|
|
||||||
qRound(totalG / static_cast<double>(rgbValues.size())),
|
|
||||||
qRound(totalB / static_cast<double>(rgbValues.size()))
|
|
||||||
);
|
|
||||||
|
|
||||||
return QList<QColor>() << avgColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto dominantChannel = findBiggestColorRange(rgbValues);
|
|
||||||
std::ranges::sort(rgbValues, [dominantChannel](const auto& a, const auto& b) {
|
|
||||||
if (dominantChannel == 'r') return a.red() < b.red();
|
|
||||||
else if (dominantChannel == 'g') return a.green() < b.green();
|
|
||||||
return a.blue() < b.blue();
|
|
||||||
});
|
|
||||||
|
|
||||||
auto mid = rgbValues.size() / 2;
|
|
||||||
|
|
||||||
auto leftHalf = rgbValues.mid(0, mid);
|
|
||||||
auto rightHalf = rgbValues.mid(mid);
|
|
||||||
|
|
||||||
QList<QColor> result;
|
|
||||||
result.append(quantization(leftHalf, depth + 1));
|
|
||||||
result.append(quantization(rightHalf, depth + 1));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
char ColorQuantizerOperation::findBiggestColorRange(const QList<QColor>& rgbValues) {
|
|
||||||
if (rgbValues.isEmpty()) return 'r';
|
|
||||||
|
|
||||||
auto rMin = 255;
|
|
||||||
auto gMin = 255;
|
|
||||||
auto bMin = 255;
|
|
||||||
auto rMax = 0;
|
|
||||||
auto gMax = 0;
|
|
||||||
auto bMax = 0;
|
|
||||||
|
|
||||||
for (const auto& color: rgbValues) {
|
|
||||||
rMin = qMin(rMin, color.red());
|
|
||||||
gMin = qMin(gMin, color.green());
|
|
||||||
bMin = qMin(bMin, color.blue());
|
|
||||||
|
|
||||||
rMax = qMax(rMax, color.red());
|
|
||||||
gMax = qMax(gMax, color.green());
|
|
||||||
bMax = qMax(bMax, color.blue());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto rRange = rMax - rMin;
|
|
||||||
auto gRange = gMax - gMin;
|
|
||||||
auto bRange = bMax - bMin;
|
|
||||||
|
|
||||||
auto biggestRange = qMax(rRange, qMax(gRange, bRange));
|
|
||||||
if (biggestRange == rRange) {
|
|
||||||
return 'r';
|
|
||||||
} else if (biggestRange == gRange) {
|
|
||||||
return 'g';
|
|
||||||
} else {
|
|
||||||
return 'b';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorQuantizerOperation::finishRun() {
|
|
||||||
QMetaObject::invokeMethod(this, &ColorQuantizerOperation::finished, Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorQuantizerOperation::finished() {
|
|
||||||
emit this->done(colors);
|
|
||||||
delete this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorQuantizerOperation::run() {
|
|
||||||
if (!this->shouldCancel) {
|
|
||||||
this->quantizeImage();
|
|
||||||
|
|
||||||
if (this->shouldCancel.loadAcquire()) {
|
|
||||||
qCDebug(logColorQuantizer) << "Color quantization" << this << "cancelled";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this->finishRun();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorQuantizerOperation::tryCancel() { this->shouldCancel.storeRelease(true); }
|
|
||||||
|
|
||||||
void ColorQuantizer::componentComplete() {
|
|
||||||
componentCompleted = true;
|
|
||||||
if (!mSource.isEmpty()) quantizeAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorQuantizer::setSource(const QUrl& source) {
|
|
||||||
if (mSource != source) {
|
|
||||||
mSource = source;
|
|
||||||
emit this->sourceChanged();
|
|
||||||
|
|
||||||
if (this->componentCompleted && !mSource.isEmpty()) quantizeAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorQuantizer::setDepth(qreal depth) {
|
|
||||||
if (mDepth != depth) {
|
|
||||||
mDepth = depth;
|
|
||||||
emit this->depthChanged();
|
|
||||||
|
|
||||||
if (this->componentCompleted) quantizeAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorQuantizer::setRescaleSize(int rescaleSize) {
|
|
||||||
if (mRescaleSize != rescaleSize) {
|
|
||||||
mRescaleSize = rescaleSize;
|
|
||||||
emit this->rescaleSizeChanged();
|
|
||||||
|
|
||||||
if (this->componentCompleted) quantizeAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorQuantizer::operationFinished(const QList<QColor>& result) {
|
|
||||||
bColors = result;
|
|
||||||
this->liveOperation = nullptr;
|
|
||||||
emit this->colorsChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorQuantizer::quantizeAsync() {
|
|
||||||
if (this->liveOperation) this->cancelAsync();
|
|
||||||
|
|
||||||
qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
|
|
||||||
this->liveOperation = new ColorQuantizerOperation(&mSource, mDepth, mRescaleSize);
|
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
this->liveOperation,
|
|
||||||
&ColorQuantizerOperation::done,
|
|
||||||
this,
|
|
||||||
&ColorQuantizer::operationFinished
|
|
||||||
);
|
|
||||||
|
|
||||||
QThreadPool::globalInstance()->start(this->liveOperation);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorQuantizer::cancelAsync() {
|
|
||||||
if (!this->liveOperation) return;
|
|
||||||
|
|
||||||
this->liveOperation->tryCancel();
|
|
||||||
QThreadPool::globalInstance()->waitForDone();
|
|
||||||
|
|
||||||
QObject::disconnect(this->liveOperation, nullptr, this, nullptr);
|
|
||||||
this->liveOperation = nullptr;
|
|
||||||
}
|
|
|
@ -1,128 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <qlist.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qproperty.h>
|
|
||||||
#include <qqmlintegration.h>
|
|
||||||
#include <qqmlparserstatus.h>
|
|
||||||
#include <qrunnable.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
|
|
||||||
class ColorQuantizerOperation
|
|
||||||
: public QObject
|
|
||||||
, public QRunnable {
|
|
||||||
Q_OBJECT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize);
|
|
||||||
|
|
||||||
void run() override;
|
|
||||||
void tryCancel();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void done(QList<QColor> colors);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void finished();
|
|
||||||
|
|
||||||
private:
|
|
||||||
static char findBiggestColorRange(const QList<QColor>& rgbValues);
|
|
||||||
|
|
||||||
void quantizeImage(const QAtomicInteger<bool>& shouldCancel = false);
|
|
||||||
|
|
||||||
QList<QColor> quantization(
|
|
||||||
QList<QColor>& rgbValues,
|
|
||||||
qreal depth,
|
|
||||||
const QAtomicInteger<bool>& shouldCancel = false
|
|
||||||
);
|
|
||||||
|
|
||||||
void finishRun();
|
|
||||||
|
|
||||||
QAtomicInteger<bool> shouldCancel = false;
|
|
||||||
QList<QColor> colors;
|
|
||||||
QUrl* source;
|
|
||||||
qreal maxDepth;
|
|
||||||
qreal rescaleSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
///! Color Quantization Utility
|
|
||||||
/// A color quantization utility used for getting prevalent colors in an image, by
|
|
||||||
/// averaging out the image's color data recursively.
|
|
||||||
///
|
|
||||||
/// #### Example
|
|
||||||
/// ```qml
|
|
||||||
/// ColorQuantizer {
|
|
||||||
/// id: colorQuantizer
|
|
||||||
/// source: Qt.resolvedUrl("./yourImage.png")
|
|
||||||
/// depth: 3 // Will produce 8 colors (2³)
|
|
||||||
/// rescaleSize: 64 // Rescale to 64x64 for faster processing
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
class ColorQuantizer
|
|
||||||
: public QObject
|
|
||||||
, public QQmlParserStatus {
|
|
||||||
|
|
||||||
Q_OBJECT;
|
|
||||||
QML_ELEMENT;
|
|
||||||
Q_INTERFACES(QQmlParserStatus);
|
|
||||||
/// Access the colors resulting from the color quantization performed.
|
|
||||||
/// > [!NOTE] The amount of colors returned from the quantization is determined by
|
|
||||||
/// > the property depth, specifically 2ⁿ where n is the depth.
|
|
||||||
Q_PROPERTY(QList<QColor> colors READ default NOTIFY colorsChanged BINDABLE bindableColors);
|
|
||||||
|
|
||||||
/// Path to the image you'd like to run the color quantization on.
|
|
||||||
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged);
|
|
||||||
|
|
||||||
/// Max depth for the color quantization. Each level of depth represents another
|
|
||||||
/// binary split of the color space
|
|
||||||
Q_PROPERTY(qreal depth READ depth WRITE setDepth NOTIFY depthChanged);
|
|
||||||
|
|
||||||
/// The size to rescale the image to, when rescaleSize is 0 then no scaling will be done.
|
|
||||||
/// > [!NOTE] Results from color quantization doesn't suffer much when rescaling, it's
|
|
||||||
/// > reccommended to rescale, otherwise the quantization process will take much longer.
|
|
||||||
Q_PROPERTY(qreal rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged);
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ColorQuantizer(QObject* parent = nullptr): QObject(parent) {}
|
|
||||||
|
|
||||||
void componentComplete() override;
|
|
||||||
void classBegin() override {}
|
|
||||||
|
|
||||||
[[nodiscard]] QBindable<QList<QColor>> bindableColors() { return &this->bColors; }
|
|
||||||
|
|
||||||
[[nodiscard]] QUrl source() const { return mSource; }
|
|
||||||
void setSource(const QUrl& source);
|
|
||||||
|
|
||||||
[[nodiscard]] qreal depth() const { return mDepth; }
|
|
||||||
void setDepth(qreal depth);
|
|
||||||
|
|
||||||
[[nodiscard]] qreal rescaleSize() const { return mRescaleSize; }
|
|
||||||
void setRescaleSize(int rescaleSize);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void colorsChanged();
|
|
||||||
void sourceChanged();
|
|
||||||
void depthChanged();
|
|
||||||
void rescaleSizeChanged();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void operationFinished(const QList<QColor>& result);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void quantizeAsync();
|
|
||||||
void cancelAsync();
|
|
||||||
|
|
||||||
bool componentCompleted = false;
|
|
||||||
ColorQuantizerOperation* liveOperation = nullptr;
|
|
||||||
QUrl mSource;
|
|
||||||
qreal mDepth = 0;
|
|
||||||
qreal mRescaleSize = 0;
|
|
||||||
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(
|
|
||||||
ColorQuantizer,
|
|
||||||
QList<QColor>,
|
|
||||||
bColors,
|
|
||||||
&ColorQuantizer::colorsChanged
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -29,6 +29,5 @@ headers = [
|
||||||
"qsmenuanchor.hpp",
|
"qsmenuanchor.hpp",
|
||||||
"clock.hpp",
|
"clock.hpp",
|
||||||
"scriptmodel.hpp",
|
"scriptmodel.hpp",
|
||||||
"colorquantizer.hpp",
|
|
||||||
]
|
]
|
||||||
-----
|
-----
|
||||||
|
|
|
@ -333,7 +333,6 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
|
||||||
auto* monitor = this->findMonitorByName(name, true);
|
auto* monitor = this->findMonitorByName(name, true);
|
||||||
this->setFocusedMonitor(monitor);
|
this->setFocusedMonitor(monitor);
|
||||||
monitor->setActiveWorkspace(workspace);
|
monitor->setActiveWorkspace(workspace);
|
||||||
qCDebug(logHyprlandIpc) << "Monitor" << name << "focused with workspace" << workspace->id();
|
|
||||||
} else if (event->name == "workspacev2") {
|
} else if (event->name == "workspacev2") {
|
||||||
auto args = event->parseView(2);
|
auto args = event->parseView(2);
|
||||||
auto id = args.at(0).toInt();
|
auto id = args.at(0).toInt();
|
||||||
|
@ -342,8 +341,6 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
|
||||||
if (this->mFocusedMonitor != nullptr) {
|
if (this->mFocusedMonitor != nullptr) {
|
||||||
auto* workspace = this->findWorkspaceByName(name, true, id);
|
auto* workspace = this->findWorkspaceByName(name, true, id);
|
||||||
this->mFocusedMonitor->setActiveWorkspace(workspace);
|
this->mFocusedMonitor->setActiveWorkspace(workspace);
|
||||||
qCDebug(logHyprlandIpc) << "Workspace" << id << "activated on"
|
|
||||||
<< this->mFocusedMonitor->name();
|
|
||||||
}
|
}
|
||||||
} else if (event->name == "moveworkspacev2") {
|
} else if (event->name == "moveworkspacev2") {
|
||||||
auto args = event->parseView(3);
|
auto args = event->parseView(3);
|
||||||
|
@ -354,7 +351,6 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
|
||||||
auto* workspace = this->findWorkspaceByName(name, true, id);
|
auto* workspace = this->findWorkspaceByName(name, true, id);
|
||||||
auto* monitor = this->findMonitorByName(monitorName, true);
|
auto* monitor = this->findMonitorByName(monitorName, true);
|
||||||
|
|
||||||
qCDebug(logHyprlandIpc) << "Workspace" << id << "moved to monitor" << monitorName;
|
|
||||||
workspace->setMonitor(monitor);
|
workspace->setMonitor(monitor);
|
||||||
} else if (event->name == "renameworkspace") {
|
} else if (event->name == "renameworkspace") {
|
||||||
auto args = event->parseView(2);
|
auto args = event->parseView(2);
|
||||||
|
@ -378,28 +374,15 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
|
||||||
HyprlandWorkspace*
|
HyprlandWorkspace*
|
||||||
HyprlandIpc::findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id) {
|
HyprlandIpc::findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id) {
|
||||||
const auto& mList = this->mWorkspaces.valueList();
|
const auto& mList = this->mWorkspaces.valueList();
|
||||||
HyprlandWorkspace* workspace = nullptr;
|
|
||||||
|
|
||||||
if (id != -1) {
|
auto workspaceIter =
|
||||||
auto workspaceIter =
|
std::ranges::find_if(mList, [name](const HyprlandWorkspace* m) { return m->name() == name; });
|
||||||
std::ranges::find_if(mList, [&](const HyprlandWorkspace* m) { return m->id() == id; });
|
|
||||||
|
|
||||||
workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter;
|
if (workspaceIter != mList.end()) {
|
||||||
}
|
return *workspaceIter;
|
||||||
|
|
||||||
if (!workspace) {
|
|
||||||
auto workspaceIter =
|
|
||||||
std::ranges::find_if(mList, [&](const HyprlandWorkspace* m) { return m->name() == name; });
|
|
||||||
|
|
||||||
workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (workspace) {
|
|
||||||
return workspace;
|
|
||||||
} else if (createIfMissing) {
|
} else if (createIfMissing) {
|
||||||
qCDebug(logHyprlandIpc) << "Workspace" << name
|
qCDebug(logHyprlandIpc) << "Workspace" << name
|
||||||
<< "requested before creation, performing early init with id" << id;
|
<< "requested before creation, performing early init";
|
||||||
|
|
||||||
auto* workspace = new HyprlandWorkspace(this);
|
auto* workspace = new HyprlandWorkspace(this);
|
||||||
workspace->updateInitial(id, name);
|
workspace->updateInitial(id, name);
|
||||||
this->mWorkspaces.insertObject(workspace);
|
this->mWorkspaces.insertObject(workspace);
|
||||||
|
@ -417,34 +400,24 @@ void HyprlandIpc::refreshWorkspaces(bool canCreate) {
|
||||||
this->requestingWorkspaces = false;
|
this->requestingWorkspaces = false;
|
||||||
if (!success) return;
|
if (!success) return;
|
||||||
|
|
||||||
qCDebug(logHyprlandIpc) << "Parsing workspaces response";
|
qCDebug(logHyprlandIpc) << "parsing workspaces response";
|
||||||
auto json = QJsonDocument::fromJson(resp).array();
|
auto json = QJsonDocument::fromJson(resp).array();
|
||||||
|
|
||||||
const auto& mList = this->mWorkspaces.valueList();
|
const auto& mList = this->mWorkspaces.valueList();
|
||||||
auto ids = QVector<quint32>();
|
auto names = QVector<QString>();
|
||||||
|
|
||||||
for (auto entry: json) {
|
for (auto entry: json) {
|
||||||
auto object = entry.toObject().toVariantMap();
|
auto object = entry.toObject().toVariantMap();
|
||||||
|
auto name = object.value("name").toString();
|
||||||
|
|
||||||
auto id = object.value("id").toInt();
|
auto workspaceIter = std::ranges::find_if(mList, [name](const HyprlandWorkspace* m) {
|
||||||
|
return m->name() == name;
|
||||||
auto workspaceIter =
|
});
|
||||||
std::ranges::find_if(mList, [&](const HyprlandWorkspace* m) { return m->id() == id; });
|
|
||||||
|
|
||||||
// Only fall back to name-based filtering as a last resort, for workspaces where
|
|
||||||
// no ID has been determined yet.
|
|
||||||
if (workspaceIter == mList.end()) {
|
|
||||||
auto name = object.value("name").toString();
|
|
||||||
|
|
||||||
workspaceIter = std::ranges::find_if(mList, [&](const HyprlandWorkspace* m) {
|
|
||||||
return m->id() == -1 && m->name() == name;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter;
|
auto* workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter;
|
||||||
auto existed = workspace != nullptr;
|
auto existed = workspace != nullptr;
|
||||||
|
|
||||||
if (!existed) {
|
if (workspace == nullptr) {
|
||||||
if (!canCreate) continue;
|
if (!canCreate) continue;
|
||||||
workspace = new HyprlandWorkspace(this);
|
workspace = new HyprlandWorkspace(this);
|
||||||
}
|
}
|
||||||
|
@ -455,22 +428,20 @@ void HyprlandIpc::refreshWorkspaces(bool canCreate) {
|
||||||
this->mWorkspaces.insertObject(workspace);
|
this->mWorkspaces.insertObject(workspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
ids.push_back(id);
|
names.push_back(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canCreate) {
|
auto removedWorkspaces = QVector<HyprlandWorkspace*>();
|
||||||
auto removedWorkspaces = QVector<HyprlandWorkspace*>();
|
|
||||||
|
|
||||||
for (auto* workspace: mList) {
|
for (auto* workspace: mList) {
|
||||||
if (!ids.contains(workspace->id())) {
|
if (!names.contains(workspace->name())) {
|
||||||
removedWorkspaces.push_back(workspace);
|
removedWorkspaces.push_back(workspace);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (auto* workspace: removedWorkspaces) {
|
for (auto* workspace: removedWorkspaces) {
|
||||||
this->mWorkspaces.removeObject(workspace);
|
this->mWorkspaces.removeObject(workspace);
|
||||||
delete workspace;
|
delete workspace;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,7 @@ public:
|
||||||
[[nodiscard]] ObjectModel<HyprlandWorkspace>* workspaces();
|
[[nodiscard]] ObjectModel<HyprlandWorkspace>* workspaces();
|
||||||
|
|
||||||
// No byId because these preemptively create objects. The given id is set if created.
|
// No byId because these preemptively create objects. The given id is set if created.
|
||||||
HyprlandWorkspace* findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id = -1);
|
HyprlandWorkspace* findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id = 0);
|
||||||
HyprlandMonitor* findMonitorByName(const QString& name, bool createIfMissing, qint32 id = -1);
|
HyprlandMonitor* findMonitorByName(const QString& name, bool createIfMissing, qint32 id = -1);
|
||||||
|
|
||||||
// canCreate avoids making ghost workspaces when the connection races
|
// canCreate avoids making ghost workspaces when the connection races
|
||||||
|
|
|
@ -117,8 +117,6 @@ void HyprlandMonitor::setActiveWorkspace(HyprlandWorkspace* workspace) {
|
||||||
this->mActiveWorkspace = workspace;
|
this->mActiveWorkspace = workspace;
|
||||||
|
|
||||||
if (workspace != nullptr) {
|
if (workspace != nullptr) {
|
||||||
workspace->setMonitor(this);
|
|
||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
workspace,
|
workspace,
|
||||||
&QObject::destroyed,
|
&QObject::destroyed,
|
||||||
|
|
|
@ -35,22 +35,18 @@ void HyprlandWorkspace::updateInitial(qint32 id, QString name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HyprlandWorkspace::updateFromObject(QVariantMap object) {
|
void HyprlandWorkspace::updateFromObject(QVariantMap object) {
|
||||||
|
auto id = object.value("id").value<qint32>();
|
||||||
auto name = object.value("name").value<QString>();
|
auto name = object.value("name").value<QString>();
|
||||||
auto monitorId = object.value("monitorID").value<qint32>();
|
auto monitorId = object.value("monitorID").value<qint32>();
|
||||||
auto monitorName = object.value("monitor").value<QString>();
|
auto monitorName = object.value("monitor").value<QString>();
|
||||||
|
|
||||||
auto initial = this->mId = -1;
|
if (id != this->mId) {
|
||||||
|
this->mId = id;
|
||||||
// ID cannot be updated after creation
|
|
||||||
if (initial) {
|
|
||||||
this->mId = object.value("id").value<qint32>();
|
|
||||||
emit this->idChanged();
|
emit this->idChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// No events we currently handle give a workspace id but not a name,
|
if (name != this->mName) {
|
||||||
// so we shouldn't set this if it isn't an initial query
|
this->mName = std::move(name);
|
||||||
if (initial && name != this->mName) {
|
|
||||||
this->mName = name;
|
|
||||||
emit this->nameChanged();
|
emit this->nameChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue