21 Commits
Author SHA1 Message Date
AramJonghu 87c9f85ac7 fix(hyprextras): false flag recursion
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Python / lint-format (pull_request) Successful in 21s
Python / test (pull_request) Successful in 47s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m53s
C++ / build (pull_request) Successful in 2m22s
2026-06-30 03:38:39 +02:00
AramJonghu 532e5a0a03 clang-tidy: reordering 2026-06-30 03:38:16 +02:00
AramJonghu dcc535d213 unused nolint
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 15s
Python / test (pull_request) Successful in 46s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m46s
C++ / build (pull_request) Successful in 2m26s
2026-06-30 03:27:01 +02:00
AramJonghu e347671edd missing commas clang-tidy 2026-06-30 03:23:36 +02:00
AramJonghu d4e4152bdc leftover format file
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 19s
Python / lint-format (pull_request) Successful in 25s
Python / test (pull_request) Successful in 46s
C++ / build (pull_request) Successful in 3m19s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m9s
2026-06-30 03:18:42 +02:00
AramJonghu 728bdc85bc fix(clang-tidy/analyzer): resolving false positives
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 26s
Python / test (pull_request) Successful in 47s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m54s
C++ / build (pull_request) Successful in 3m25s
2026-06-30 03:17:52 +02:00
AramJonghu 944694966b tidy(blobmaterial&circularindicatormanager): warning fixes, readability and modernize headers.
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 48s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m41s
C++ / build (pull_request) Successful in 2m29s
2026-06-30 03:08:32 +02:00
AramJonghu 1e31e14de8 format(remainder): clang-format remainder of files
Python / lint-format (pull_request) Successful in 27s
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 48s
Python / test (pull_request) Successful in 1m2s
C++ / build (pull_request) Successful in 2m15s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m25s
2026-06-30 02:10:37 +02:00
AramJonghu 658be09da2 fix(buttonrow): restore private specifier removed by clang-format 2026-06-30 02:10:17 +02:00
AramJonghu 467e44bb9f tidy&format(all): all files formatted and relevant warnings resolved
C++ / build (pull_request) Failing after 15s
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 28s
Python / lint-format (pull_request) Successful in 45s
Python / test (pull_request) Successful in 38s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m45s
2026-06-30 02:04:24 +02:00
AramJonghu fed9372b35 refactor(tickingservice): flatten nested if blocks with early returns 2026-06-30 01:54:22 +02:00
AramJonghu b2cddea636 fix(gpu): use epsilon comparison instead of == for floating-point guards 2026-06-30 01:36:40 +02:00
AramJonghu 396825d785 fix(desktopmodel): cast qsizetype to int to suppress -Wshorten-64-to-32 2026-06-30 01:36:14 +02:00
AramJonghu f439456ff4 fix(strokecanvasrenderer): use qreal instead of float in drawHoverCursor
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 27s
Python / lint-format (pull_request) Successful in 31s
Python / test (pull_request) Successful in 1m0s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m39s
C++ / build (pull_request) Successful in 2m17s
2026-06-30 01:26:19 +02:00
AramJonghu 4a18edfc78 fix(strokecanvasitem): use qreal instead of float to suppress -Wdouble-promotion
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 16s
Python / lint-format (pull_request) Successful in 23s
Python / test (pull_request) Successful in 48s
C++ / build (pull_request) Successful in 2m20s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m59s
2026-06-30 01:22:40 +02:00
AramJonghu 3eecbc16bc perf(lazylistview): collect pending indices and hoist layoutSize cast in updatePolishdbus workaround for session 2026-06-30 00:23:23 +02:00
AramJonghu b939224e9d action secret added so we have push permissions for docker
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 25s
Python / lint-format (pull_request) Successful in 28s
Python / test (pull_request) Successful in 46s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m53s
C++ / build (pull_request) Successful in 2m31s
2026-06-29 23:01:54 +02:00
AramJonghu 644a1e0c78 workflow update; node and docker deps added
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 52s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m40s
C++ / build (pull_request) Successful in 2m43s
2026-06-29 22:40:28 +02:00
AramJonghuandzach bc9d8af0fe update to clang-tidy + workflow improvement cpp build & lint check (#130)
### Goal

Idea is to reduce the annoyances and research which clang-tidy rules make sense and actually have benefits in our project.

Also considering immediately resolving most if not all tidy warnings.

### Current Changes

- `.clang-tidy` has changes to follow the guidelines we have been following silently. Some rules were different from how we program.
    - Note that naming is completely removed and won't be checked. In a future PR (where we go over the code to fix linting issues), we can reintroduce them and make sure they fit our codebase consistently.
- tidy rules are not considered errors anymore (stopped compile).
- CI/CD addition to build check and clang-tidy check. Tried to get clang-tidy check to show when failed, but that causes complications in its checks; so now it is simply silent when failing.
- Dockerfile that creates an arch container as cache for the CI/CD, prevents redownload of dependencies each run. Instead, it runs weekly (or optionally a different interval).

### Current complications

- To push the docker container build, it would require this webserver to allow larger filesize pushes (need ~2GB). Current workaround is arguably better where docker build is pushed to runner server.
- Figuring out which clang-tidy rules make sense.

Reviewed-on: #130
Co-authored-by: AramJonghu <aramjonghu@tutamail.com>
Co-committed-by: AramJonghu <aramjonghu@tutamail.com>
2026-06-29 22:10:26 +02:00
zach 7d65578a5f proper VERSION var 2026-06-29 19:37:11 +02:00
zach f883b3d7fe versioning 2026-06-29 18:57:32 +02:00
99 changed files with 4876 additions and 4172 deletions
+3 -3
View File
@@ -1,15 +1,15 @@
--- ---
BasedOnStyle: LLVM BasedOnStyle: LLVM
AccessModifierOffset: 0 AccessModifierOffset: 0
AlignAfterOpenBracket: Align AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: false AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false AlignConsecutiveDeclarations: false
AlignOperands: Align AlignOperands: Align
AllowAllParametersOfDeclarationOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true
AllowShortFunctionsOnASingleLine: Inline AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: Never AllowShortIfStatementsOnASingleLine: WithoutElse
AlwaysBreakBeforeMultilineStrings: false AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes AlwaysBreakTemplateDeclarations: MultiLine
BinPackArguments: false BinPackArguments: false
BinPackParameters: false BinPackParameters: false
BreakBeforeBraces: Attach BreakBeforeBraces: Attach
+15 -19
View File
@@ -2,29 +2,25 @@
Checks: > Checks: >
-*, -*,
bugprone-*, bugprone-*,
-bugprone-throwing-static-initialization,
-bugprone-invalid-enum-default-initialization,
-bugprone-suspicious-include,
-bugprone-branch-clone,
-bugprone-easily-swappable-parameters,
-bugprone-narrowing-conversions,
-bugprone-implicit-widening-of-multiplication-result,
clang-analyzer-*, clang-analyzer-*,
modernize-*, cppcoreguidelines-init-variables,
-modernize-use-trailing-return-type, misc-no-recursion,
performance-*, concurrency-mt-unsafe,
readability-braces-around-statements,
readability-else-after-return, readability-else-after-return,
readability-identifier-naming, readability-make-member-function-const,
readability-redundant-*, readability-redundant-*,
-readability-redundant-inline-specifier,
readability-simplify-*, readability-simplify-*,
CheckOptions: modernize-use-override,
readability-identifier-naming.ClassCase: CamelCase modernize-use-nullptr,
readability-identifier-naming.EnumCase: CamelCase modernize-deprecated-headers,
readability-identifier-naming.FunctionCase: camelBack
readability-identifier-naming.MemberCase: camelBack
readability-identifier-naming.MemberPrefix: m_
readability-identifier-naming.MethodCase: camelBack
readability-identifier-naming.NamespaceCase: CamelCase
readability-identifier-naming.ParameterCase: camelBack
readability-identifier-naming.PrivateMemberPrefix: m_
readability-identifier-naming.StaticConstantCase: UPPER_CASE
readability-identifier-naming.StaticConstantPrefix: k
readability-identifier-naming.VariableCase: camelBack
WarningsAsErrors: "*"
HeaderFilterRegex: ".*" HeaderFilterRegex: ".*"
FormatStyle: file FormatStyle: file
+30
View File
@@ -0,0 +1,30 @@
name: Rebuild CI Image
on:
schedule:
- cron: '0 6 * * 1'
workflow_dispatch:
jobs:
build:
runs-on: alpine
container:
image: node:26-alpine
env:
IMAGE: git.aramjonghu.nl/aramjonghu/zshell-ci:latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Docker CLI
run: apk add --no-cache docker-cli
- name: Login to registry
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.aramjonghu.nl --username aramjonghu --password-stdin
- name: Build image
run: docker build -t "$IMAGE" -f ci/Dockerfile .
- name: Push image
run: docker push "$IMAGE"
+29
View File
@@ -0,0 +1,29 @@
name: C++
on:
pull_request:
jobs:
build:
runs-on: alpine
container:
image: git.aramjonghu.nl/aramjonghu/zshell-ci:latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure
run: cmake -B build -G Ninja -DENABLE_MODULES=plugin -DCMAKE_BUILD_TYPE=Release
- name: Build
run: ninja -C build
- name: clang-tidy
continue-on-error: true
run: |
set -o pipefail
run-clang-tidy -p build -j $(nproc) -header-filter="Plugins/.*" 2>&1 | \
grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct'
+37 -8
View File
@@ -1,6 +1,35 @@
cmake_minimum_required(VERSION 3.19) cmake_minimum_required(VERSION 3.19)
project(ZShell) if(NOT DEFINED VERSION)
execute_process(
COMMAND git describe --tags --abbrev=0
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_LAST_TAG
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if (GIT_LAST_TAG)
string(REGEX REPLACE "^v" "" GIT_LAST_TAG "${GIT_LAST_TAG}")
string(REPLACE "." ";" VERSION_LIST "${GIT_LAST_TAG}")
list(GET VERSION_LIST 0 VERSION_MAJOR)
list(GET VERSION_LIST 1 VERSION_MINOR)
execute_process(
COMMAND git rev-list v${VERSION_MAJOR}.${VERSION_MINOR}.0..HEAD --count
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE VERSION_PATCH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
endif()
endif()
set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
project(ZShell VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
message(STATUS "ZShell version: ${VERSION}")
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -15,11 +44,11 @@ set(INSTALL_QSCONFDIR "etc/xdg/quickshell/zshell" CACHE STRING "Quickshell confi
set(INSTALL_GREETERCONFDIR "etc/xdg/quickshell/zshell-greeter" CACHE STRING "Quickshell greeter install dir") set(INSTALL_GREETERCONFDIR "etc/xdg/quickshell/zshell-greeter" CACHE STRING "Quickshell greeter install dir")
add_compile_options( add_compile_options(
-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wall -Wextra -Wpedantic -Wshadow -Wconversion
-Wold-style-cast -Wnull-dereference -Wdouble-promotion -Wold-style-cast -Wnull-dereference -Wdouble-promotion
-Wformat=2 -Wfloat-equal -Woverloaded-virtual -Wformat=2 -Wfloat-equal -Woverloaded-virtual
-Wsign-conversion -Wredundant-decls -Wswitch -Wsign-conversion -Wredundant-decls -Wswitch
-Wunreachable-code -Wunreachable-code
) )
@@ -30,8 +59,8 @@ endif()
if("shell" IN_LIST ENABLE_MODULES) if("shell" IN_LIST ENABLE_MODULES)
foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths) foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths)
install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}") install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}")
endforeach() endforeach()
# Disable watching for changes # Disable watching for changes
file(READ shell.qml SHELL_QML) file(READ shell.qml SHELL_QML)
+18 -22
View File
@@ -2,9 +2,7 @@
#include "blobinvertedrect.hpp" #include "blobinvertedrect.hpp"
#include "blobshape.hpp" #include "blobshape.hpp"
BlobGroup::BlobGroup(QObject* parent) BlobGroup::BlobGroup(QObject* parent) : QObject(parent) {}
: QObject(parent) {
}
BlobGroup::~BlobGroup() { BlobGroup::~BlobGroup() {
for (auto* shape : std::as_const(m_shapes)) for (auto* shape : std::as_const(m_shapes))
@@ -14,24 +12,21 @@ BlobGroup::~BlobGroup() {
} }
void BlobGroup::setSmoothing(qreal s) { void BlobGroup::setSmoothing(qreal s) {
if (qFuzzyCompare(m_smoothing, s)) if (qFuzzyCompare(m_smoothing, s)) return;
return;
m_smoothing = s; m_smoothing = s;
emit smoothingChanged(); emit smoothingChanged();
markDirty(); markDirty();
} }
void BlobGroup::setColor(const QColor& c) { void BlobGroup::setColor(const QColor& c) {
if (m_color == c) if (m_color == c) return;
return;
m_color = c; m_color = c;
emit colorChanged(); emit colorChanged();
markDirty(); markDirty();
} }
void BlobGroup::addShape(BlobShape* shape) { void BlobGroup::addShape(BlobShape* shape) {
if (!shape || m_shapes.contains(shape)) if (!shape || m_shapes.contains(shape)) return;
return;
m_shapes.append(shape); m_shapes.append(shape);
markDirty(); markDirty();
} }
@@ -42,15 +37,13 @@ void BlobGroup::removeShape(BlobShape* shape) {
} }
void BlobGroup::setInvertedRect(BlobInvertedRect* rect) { void BlobGroup::setInvertedRect(BlobInvertedRect* rect) {
if (m_invertedRect == rect) if (m_invertedRect == rect) return;
return;
m_invertedRect = rect; m_invertedRect = rect;
markDirty(); markDirty();
} }
void BlobGroup::clearInvertedRect(BlobInvertedRect* rect) { void BlobGroup::clearInvertedRect(BlobInvertedRect* rect) {
if (m_invertedRect != rect) if (m_invertedRect != rect) return;
return;
m_invertedRect = nullptr; m_invertedRect = nullptr;
markDirty(); markDirty();
} }
@@ -75,15 +68,19 @@ void BlobGroup::markShapeDirty(BlobShape* source) {
// Use cached padded rects to find spatial neighbors // Use cached padded rects to find spatial neighbors
const float pad = static_cast<float>(m_smoothing) * 2.0f; const float pad = static_cast<float>(m_smoothing) * 2.0f;
const QRectF srcRect(static_cast<double>(source->m_cachedPaddedX - pad), const QRectF srcRect(
static_cast<double>(source->m_cachedPaddedY - pad), static_cast<double>(source->m_cachedPaddedW + pad * 2.0f), static_cast<double>(source->m_cachedPaddedX - pad),
static_cast<double>(source->m_cachedPaddedH + pad * 2.0f)); static_cast<double>(source->m_cachedPaddedY - pad),
static_cast<double>(source->m_cachedPaddedW + pad * 2.0f),
static_cast<double>(source->m_cachedPaddedH + pad * 2.0f));
for (auto* shape : std::as_const(m_shapes)) { for (auto* shape : std::as_const(m_shapes)) {
if (shape == source) if (shape == source) continue;
continue; const QRectF otherRect(
const QRectF otherRect(static_cast<double>(shape->m_cachedPaddedX), static_cast<double>(shape->m_cachedPaddedY), static_cast<double>(shape->m_cachedPaddedX),
static_cast<double>(shape->m_cachedPaddedW), static_cast<double>(shape->m_cachedPaddedH)); static_cast<double>(shape->m_cachedPaddedY),
static_cast<double>(shape->m_cachedPaddedW),
static_cast<double>(shape->m_cachedPaddedH));
if (srcRect.intersects(otherRect)) { if (srcRect.intersects(otherRect)) {
shape->polish(); shape->polish();
shape->update(); shape->update();
@@ -97,8 +94,7 @@ void BlobGroup::markShapeDirty(BlobShape* source) {
} }
void BlobGroup::ensurePhysicsUpdated() { void BlobGroup::ensurePhysicsUpdated() {
if (m_physicsUpdated) if (m_physicsUpdated) return;
return;
m_physicsUpdated = true; m_physicsUpdated = true;
for (auto* shape : std::as_const(m_shapes)) for (auto* shape : std::as_const(m_shapes))
shape->updatePhysics(); shape->updatePhysics();
+33 -37
View File
@@ -9,53 +9,49 @@ class BlobShape;
class BlobInvertedRect; class BlobInvertedRect;
class BlobGroup : public QObject { class BlobGroup : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(qreal smoothing READ smoothing WRITE setSmoothing NOTIFY smoothingChanged) Q_PROPERTY(
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) qreal smoothing READ smoothing WRITE setSmoothing NOTIFY
smoothingChanged)
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
public: public:
explicit BlobGroup(QObject* parent = nullptr); explicit BlobGroup(QObject* parent = nullptr);
~BlobGroup() override; ~BlobGroup() override;
[[nodiscard]] qreal smoothing() const { [[nodiscard]] qreal smoothing() const { return m_smoothing; }
return m_smoothing;
}
void setSmoothing(qreal s); void setSmoothing(qreal s);
[[nodiscard]] QColor color() const { [[nodiscard]] QColor color() const { return m_color; }
return m_color;
}
void setColor(const QColor& c); void setColor(const QColor& c);
void addShape(BlobShape* shape); void addShape(BlobShape* shape);
void removeShape(BlobShape* shape); void removeShape(BlobShape* shape);
void setInvertedRect(BlobInvertedRect* rect); void setInvertedRect(BlobInvertedRect* rect);
void clearInvertedRect(BlobInvertedRect* rect); void clearInvertedRect(BlobInvertedRect* rect);
[[nodiscard]] const QList<BlobShape*>& shapes() const { [[nodiscard]] const QList<BlobShape*>& shapes() const { return m_shapes; }
return m_shapes;
}
[[nodiscard]] BlobInvertedRect* invertedRect() const { [[nodiscard]] BlobInvertedRect* invertedRect() const {
return m_invertedRect; return m_invertedRect;
} }
void markDirty(); void markDirty();
void markShapeDirty(BlobShape* source); void markShapeDirty(BlobShape* source);
void ensurePhysicsUpdated(); void ensurePhysicsUpdated();
signals: signals:
void smoothingChanged(); void smoothingChanged();
void colorChanged(); void colorChanged();
private: private:
qreal m_smoothing = 32.0; qreal m_smoothing = 32.0;
QColor m_color{ 0x44, 0x88, 0xff }; QColor m_color{0x44, 0x88, 0xff};
QList<BlobShape*> m_shapes; QList<BlobShape*> m_shapes;
BlobInvertedRect* m_invertedRect = nullptr; BlobInvertedRect* m_invertedRect = nullptr;
bool m_physicsUpdated = false; bool m_physicsUpdated = false;
}; };
+29 -32
View File
@@ -5,12 +5,9 @@
#include <qsggeometry.h> #include <qsggeometry.h>
#include <qsgnode.h> #include <qsgnode.h>
#include <algorithm>
#include <cstring> #include <cstring>
BlobInvertedRect::BlobInvertedRect(QQuickItem* parent) BlobInvertedRect::BlobInvertedRect(QQuickItem* parent) : BlobShape(parent) {}
: BlobShape(parent) {
}
static void setFrameIndices(quint16* idx) { static void setFrameIndices(quint16* idx) {
// Top strip: 0-1-4, 1-5-4 // Top strip: 0-1-4, 1-5-4
@@ -43,7 +40,8 @@ static void setFrameIndices(quint16* idx) {
idx[23] = 7; idx[23] = 7;
} }
QSGNode* BlobInvertedRect::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) { QSGNode* BlobInvertedRect::updatePaintNode(
QSGNode* oldNode, UpdatePaintNodeData*) {
if (!m_group) { if (!m_group) {
delete oldNode; delete oldNode;
return nullptr; return nullptr;
@@ -71,8 +69,11 @@ QSGNode* BlobInvertedRect::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData
delete oldNode; delete oldNode;
node = new QSGGeometryNode; node = new QSGGeometryNode;
auto* geometry = auto* geometry = new QSGGeometry(
new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 8, 24, QSGGeometry::UnsignedShortType); QSGGeometry::defaultAttributes_TexturedPoint2D(),
8,
24,
QSGGeometry::UnsignedShortType);
geometry->setDrawingMode(QSGGeometry::DrawTriangles); geometry->setDrawingMode(QSGGeometry::DrawTriangles);
node->setGeometry(geometry); node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry); node->setFlag(QSGNode::OwnsGeometry);
@@ -120,10 +121,17 @@ QSGNode* BlobInvertedRect::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData
material->m_color = m_group->color(); material->m_color = m_group->color();
material->m_hasInverted = m_cachedHasInverted ? 1 : 0; material->m_hasInverted = m_cachedHasInverted ? 1 : 0;
material->m_invertedRadius = m_cachedInvertedRadius; material->m_invertedRadius = m_cachedInvertedRadius;
memcpy(material->m_invertedOuter, m_cachedInvertedOuter, sizeof(m_cachedInvertedOuter)); memcpy(
memcpy(material->m_invertedInner, m_cachedInvertedInner, sizeof(m_cachedInvertedInner)); material->m_invertedOuter,
m_cachedInvertedOuter,
sizeof(m_cachedInvertedOuter));
memcpy(
material->m_invertedInner,
m_cachedInvertedInner,
sizeof(m_cachedInvertedInner));
const int count = static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16))); const int count =
static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16)));
material->m_rectCount = count; material->m_rectCount = count;
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
material->m_rects[i] = m_cachedRects[i]; material->m_rects[i] = m_cachedRects[i];
@@ -134,52 +142,41 @@ QSGNode* BlobInvertedRect::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData
} }
BlobInvertedRect::~BlobInvertedRect() { BlobInvertedRect::~BlobInvertedRect() {
if (m_group) if (m_group) m_group->clearInvertedRect(this);
m_group->clearInvertedRect(this);
} }
void BlobInvertedRect::setBorderLeft(qreal v) { void BlobInvertedRect::setBorderLeft(qreal v) {
if (qFuzzyCompare(m_borderLeft, v)) if (qFuzzyCompare(m_borderLeft, v)) return;
return;
m_borderLeft = v; m_borderLeft = v;
emit borderLeftChanged(); emit borderLeftChanged();
if (m_group) if (m_group) m_group->markDirty();
m_group->markDirty();
} }
void BlobInvertedRect::setBorderRight(qreal v) { void BlobInvertedRect::setBorderRight(qreal v) {
if (qFuzzyCompare(m_borderRight, v)) if (qFuzzyCompare(m_borderRight, v)) return;
return;
m_borderRight = v; m_borderRight = v;
emit borderRightChanged(); emit borderRightChanged();
if (m_group) if (m_group) m_group->markDirty();
m_group->markDirty();
} }
void BlobInvertedRect::setBorderTop(qreal v) { void BlobInvertedRect::setBorderTop(qreal v) {
if (qFuzzyCompare(m_borderTop, v)) if (qFuzzyCompare(m_borderTop, v)) return;
return;
m_borderTop = v; m_borderTop = v;
emit borderTopChanged(); emit borderTopChanged();
if (m_group) if (m_group) m_group->markDirty();
m_group->markDirty();
} }
void BlobInvertedRect::setBorderBottom(qreal v) { void BlobInvertedRect::setBorderBottom(qreal v) {
if (qFuzzyCompare(m_borderBottom, v)) if (qFuzzyCompare(m_borderBottom, v)) return;
return;
m_borderBottom = v; m_borderBottom = v;
emit borderBottomChanged(); emit borderBottomChanged();
if (m_group) if (m_group) m_group->markDirty();
m_group->markDirty();
} }
void BlobInvertedRect::registerWithGroup() { void BlobInvertedRect::registerWithGroup() {
if (m_group) if (m_group) m_group->setInvertedRect(this);
m_group->setInvertedRect(this);
} }
void BlobInvertedRect::unregisterFromGroup() { void BlobInvertedRect::unregisterFromGroup() {
if (m_group) if (m_group) m_group->clearInvertedRect(this);
m_group->clearInvertedRect(this);
} }
+40 -42
View File
@@ -5,60 +5,58 @@
#include <qqmlengine.h> #include <qqmlengine.h>
class BlobInvertedRect : public BlobShape { class BlobInvertedRect : public BlobShape {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(qreal borderLeft READ borderLeft WRITE setBorderLeft NOTIFY borderLeftChanged) Q_PROPERTY(
Q_PROPERTY(qreal borderRight READ borderRight WRITE setBorderRight NOTIFY borderRightChanged) qreal borderLeft READ borderLeft WRITE setBorderLeft NOTIFY
Q_PROPERTY(qreal borderTop READ borderTop WRITE setBorderTop NOTIFY borderTopChanged) borderLeftChanged)
Q_PROPERTY(qreal borderBottom READ borderBottom WRITE setBorderBottom NOTIFY borderBottomChanged) Q_PROPERTY(
qreal borderRight READ borderRight WRITE setBorderRight NOTIFY
borderRightChanged)
Q_PROPERTY(
qreal borderTop READ borderTop WRITE setBorderTop NOTIFY
borderTopChanged)
Q_PROPERTY(
qreal borderBottom READ borderBottom WRITE setBorderBottom NOTIFY
borderBottomChanged)
public: public:
explicit BlobInvertedRect(QQuickItem* parent = nullptr); explicit BlobInvertedRect(QQuickItem* parent = nullptr);
~BlobInvertedRect() override; ~BlobInvertedRect() override;
[[nodiscard]] qreal borderLeft() const { [[nodiscard]] qreal borderLeft() const { return m_borderLeft; }
return m_borderLeft;
}
void setBorderLeft(qreal v); void setBorderLeft(qreal v);
[[nodiscard]] qreal borderRight() const { [[nodiscard]] qreal borderRight() const { return m_borderRight; }
return m_borderRight;
}
void setBorderRight(qreal v); void setBorderRight(qreal v);
[[nodiscard]] qreal borderTop() const { [[nodiscard]] qreal borderTop() const { return m_borderTop; }
return m_borderTop;
}
void setBorderTop(qreal v); void setBorderTop(qreal v);
[[nodiscard]] qreal borderBottom() const { [[nodiscard]] qreal borderBottom() const { return m_borderBottom; }
return m_borderBottom;
}
void setBorderBottom(qreal v); void setBorderBottom(qreal v);
signals: signals:
void borderLeftChanged(); void borderLeftChanged();
void borderRightChanged(); void borderRightChanged();
void borderTopChanged(); void borderTopChanged();
void borderBottomChanged(); void borderBottomChanged();
protected: protected:
[[nodiscard]] bool isInvertedRect() const override { [[nodiscard]] bool isInvertedRect() const override { return true; }
return true;
}
QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override; QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override;
void registerWithGroup() override; void registerWithGroup() override;
void unregisterFromGroup() override; void unregisterFromGroup() override;
private: private:
qreal m_borderLeft = 0; qreal m_borderLeft = 0;
qreal m_borderRight = 0; qreal m_borderRight = 0;
qreal m_borderTop = 0; qreal m_borderTop = 0;
qreal m_borderBottom = 0; qreal m_borderBottom = 0;
}; };
+18 -16
View File
@@ -1,24 +1,25 @@
#include "blobmaterial.hpp" #include "blobmaterial.hpp"
#include <cmath>
#include <cstring> #include <cstring>
static_assert(sizeof(decltype(BlobRectData::excludeMask)) == sizeof(float), static_assert(
"BlobMaterial packs excludeMask into a float slot via memcpy"); sizeof(decltype(BlobRectData::excludeMask)) == sizeof(float),
"BlobMaterial packs excludeMask into a float slot via memcpy");
QSGMaterialType* BlobMaterial::type() const { QSGMaterialType* BlobMaterial::type() const {
static QSGMaterialType s_type; static QSGMaterialType s_type;
return &s_type; return &s_type;
} }
QSGMaterialShader* BlobMaterial::createShader(QSGRendererInterface::RenderMode) const { QSGMaterialShader* BlobMaterial::createShader(
QSGRendererInterface::RenderMode) const {
return new BlobMaterialShader; return new BlobMaterialShader;
} }
int BlobMaterial::compare(const QSGMaterial* other) const { int BlobMaterial::compare(const QSGMaterial* other) const {
if (this < other) if (this < other) return -1;
return -1; if (this > other) return 1;
if (this > other)
return 1;
return 0; return 0;
} }
@@ -27,7 +28,8 @@ BlobMaterialShader::BlobMaterialShader() {
setShaderFileName(FragmentStage, QStringLiteral(":/shaders/blob.frag.qsb")); setShaderFileName(FragmentStage, QStringLiteral(":/shaders/blob.frag.qsb"));
} }
bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) { bool BlobMaterialShader::updateUniformData(
RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) {
Q_UNUSED(oldMaterial); Q_UNUSED(oldMaterial);
auto* mat = static_cast<BlobMaterial*>(newMaterial); auto* mat = static_cast<BlobMaterial*>(newMaterial);
QByteArray* buf = state.uniformData(); QByteArray* buf = state.uniformData();
@@ -59,10 +61,10 @@ bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newM
// Color as vec4 (offset 96, 16 bytes) // Color as vec4 (offset 96, 16 bytes)
const float color[4] = { const float color[4] = {
static_cast<float>(mat->m_color.redF()), mat->m_color.redF(),
static_cast<float>(mat->m_color.greenF()), mat->m_color.greenF(),
static_cast<float>(mat->m_color.blueF()), mat->m_color.blueF(),
static_cast<float>(mat->m_color.alphaF()), mat->m_color.alphaF(),
}; };
memcpy(buf->data() + 96, color, 16); memcpy(buf->data() + 96, color, 16);
@@ -86,11 +88,11 @@ bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newM
const auto& r = mat->m_rects[i]; const auto& r = mat->m_rects[i];
const int base = 160 + i * 80; const int base = 160 + i * 80;
// Pack excludeMask into props.x via bit-cast (read in shader with floatBitsToInt) // Pack excludeMask into props.x via bit-cast (read in shader with floatBitsToInt)
float maskAsFloat; float maskAsFloat = NAN;
memcpy(&maskAsFloat, &r.excludeMask, sizeof(float)); memcpy(&maskAsFloat, &r.excludeMask, sizeof(float));
const float d0[4] = { r.cx, r.cy, r.hw, r.hh }; const float d0[4] = {r.cx, r.cy, r.hw, r.hh};
const float d1[4] = { maskAsFloat, r.offsetX, r.offsetY, r.minEig }; const float d1[4] = {maskAsFloat, r.offsetX, r.offsetY, r.minEig};
const float d3[4] = { r.screenHalfX, r.screenHalfY, 0.0f, 0.0f }; const float d3[4] = {r.screenHalfX, r.screenHalfY, 0.0f, 0.0f};
memcpy(buf->data() + base, d0, 16); memcpy(buf->data() + base, d0, 16);
memcpy(buf->data() + base + 16, d1, 16); memcpy(buf->data() + base + 16, d1, 16);
memcpy(buf->data() + base + 32, r.invDeform, 16); memcpy(buf->data() + base + 32, r.invDeform, 16);
+26 -22
View File
@@ -9,39 +9,43 @@ struct BlobRectData {
float offsetX = 0, offsetY = 0; float offsetX = 0, offsetY = 0;
float minEig = 1.0f; float minEig = 1.0f;
// Inverse of 2x2 deformation matrix, column-major for GLSL // Inverse of 2x2 deformation matrix, column-major for GLSL
float invDeform[4] = { 1, 0, 0, 1 }; float invDeform[4] = {1, 0, 0, 1};
// Screen-space AABB half-extents of the deformed rect // Screen-space AABB half-extents of the deformed rect
float screenHalfX = 0, screenHalfY = 0; float screenHalfX = 0, screenHalfY = 0;
// Effective per-corner radii (tr, br, bl, tl), pre-computed on CPU // Effective per-corner radii (tr, br, bl, tl), pre-computed on CPU
float radius[4] = { 0, 0, 0, 0 }; float radius[4] = {0, 0, 0, 0};
// Bitmask of indices in this rect's m_cachedRects that mutually exclude (or are excluded by) this rect. // Bitmask of indices in this rect's m_cachedRects that mutually exclude (or are excluded by) this rect.
// Used by the shader to skip smin between excluded pairs. // Used by the shader to skip smin between excluded pairs.
int excludeMask = 0; int excludeMask = 0;
}; };
class BlobMaterial : public QSGMaterial { class BlobMaterial : public QSGMaterial {
public: public:
[[nodiscard]] QSGMaterialType* type() const override; [[nodiscard]] QSGMaterialType* type() const override;
[[nodiscard]] QSGMaterialShader* createShader(QSGRendererInterface::RenderMode) const override; [[nodiscard]] QSGMaterialShader* createShader(
int compare(const QSGMaterial* other) const override; QSGRendererInterface::RenderMode) const override;
int compare(const QSGMaterial* other) const override;
float m_paddedX = 0; float m_paddedX = 0;
float m_paddedY = 0; float m_paddedY = 0;
float m_paddedW = 0; float m_paddedW = 0;
float m_paddedH = 0; float m_paddedH = 0;
float m_smoothFactor = 32.0f; float m_smoothFactor = 32.0f;
int m_rectCount = 0; int m_rectCount = 0;
int m_myIndex = -2; int m_myIndex = -2;
QColor m_color{ 0x44, 0x88, 0xff }; QColor m_color{0x44, 0x88, 0xff};
int m_hasInverted = 0; int m_hasInverted = 0;
float m_invertedRadius = 0; float m_invertedRadius = 0;
float m_invertedOuter[4] = {}; float m_invertedOuter[4] = {};
float m_invertedInner[4] = {}; float m_invertedInner[4] = {};
BlobRectData m_rects[16] = {}; BlobRectData m_rects[16] = {};
}; };
class BlobMaterialShader : public QSGMaterialShader { class BlobMaterialShader : public QSGMaterialShader {
public: public:
BlobMaterialShader(); BlobMaterialShader();
bool updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) override; bool updateUniformData(
RenderState& state,
QSGMaterial* newMaterial,
QSGMaterial* oldMaterial) override;
}; };
+69 -59
View File
@@ -4,19 +4,18 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
BlobRect::BlobRect(QQuickItem* parent) BlobRect::BlobRect(QQuickItem* parent) : BlobShape(parent) {}
: BlobShape(parent) {
}
BlobRect::~BlobRect() { BlobRect::~BlobRect() {
if (m_group) if (m_group) m_group->removeShape(this);
m_group->removeShape(this);
} }
void BlobRect::updatePolish() { void BlobRect::updatePolish() {
if (m_physicsActive) { if (m_physicsActive) {
float totalDelta = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) + std::abs(m_dm11 - 1.0f); float totalDelta = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) +
float totalVel = std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11); std::abs(m_dm11 - 1.0f);
float totalVel =
std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11);
if (totalDelta < 0.004f && totalVel < 0.05f) { if (totalDelta < 0.004f && totalVel < 0.05f) {
m_dm00 = 1.0f; m_dm00 = 1.0f;
@@ -32,18 +31,16 @@ void BlobRect::updatePolish() {
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
this, this,
[this]() { [this]() {
if (m_group) if (m_group) m_group->markDirty();
m_group->markDirty(); },
},
Qt::QueuedConnection); Qt::QueuedConnection);
} }
} else { } else {
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
this, this,
[this]() { [this]() {
if (m_physicsActive && m_group) if (m_physicsActive && m_group) m_group->markDirty();
m_group->markDirty(); },
},
Qt::QueuedConnection); Qt::QueuedConnection);
} }
} }
@@ -64,20 +61,20 @@ void BlobRect::updatePhysics() {
const float dt = static_cast<float>(m_elapsed.restart()) / 1000.0f; const float dt = static_cast<float>(m_elapsed.restart()) / 1000.0f;
if (dt > 0.1f || dt < 0.001f) { if (dt > 0.1f || dt < 0.001f) {
m_prevScenePos = scenePos; m_prevScenePos = scenePos;
if (m_physicsActive) if (m_physicsActive) checkAtRest(0.0f);
checkAtRest(0.0f);
return; return;
} }
const float velX = static_cast<float>(scenePos.x() - m_prevScenePos.x()) / dt; const float velX =
const float velY = static_cast<float>(scenePos.y() - m_prevScenePos.y()) / dt; static_cast<float>(scenePos.x() - m_prevScenePos.x()) / dt;
const float velY =
static_cast<float>(scenePos.y() - m_prevScenePos.y()) / dt;
m_prevScenePos = scenePos; m_prevScenePos = scenePos;
const float speed = std::sqrt(velX * velX + velY * velY); const float speed = std::sqrt(velX * velX + velY * velY);
if (!m_physicsActive) { if (!m_physicsActive) {
if (speed < 5.0f) if (speed < 5.0f) return;
return;
m_physicsActive = true; m_physicsActive = true;
} }
@@ -89,7 +86,8 @@ void BlobRect::updatePhysics() {
float target11 = 1.0f; float target11 = 1.0f;
if (speed > 5.0f) { if (speed > 5.0f) {
const float targetStretch = 1.0f + std::min(speed * kStretchFactor, kMaxStretch); const float targetStretch =
1.0f + std::min(speed * kStretchFactor, kMaxStretch);
const float targetCompress = 1.0f / targetStretch; const float targetCompress = 1.0f / targetStretch;
const float cosA = velX / speed; const float cosA = velX / speed;
@@ -106,19 +104,23 @@ void BlobRect::updatePhysics() {
const float kStiffness = static_cast<float>(m_stiffness); const float kStiffness = static_cast<float>(m_stiffness);
const float kDamping = static_cast<float>(m_damping); const float kDamping = static_cast<float>(m_damping);
const float accel00 = -kStiffness * (m_dm00 - target00) - kDamping * m_dmVel00; const float accel00 =
-kStiffness * (m_dm00 - target00) - kDamping * m_dmVel00;
m_dmVel00 += accel00 * dt; m_dmVel00 += accel00 * dt;
m_dm00 += m_dmVel00 * dt; m_dm00 += m_dmVel00 * dt;
const float accel01 = -kStiffness * (m_dm01 - target01) - kDamping * m_dmVel01; const float accel01 =
-kStiffness * (m_dm01 - target01) - kDamping * m_dmVel01;
m_dmVel01 += accel01 * dt; m_dmVel01 += accel01 * dt;
m_dm01 += m_dmVel01 * dt; m_dm01 += m_dmVel01 * dt;
const float accel11 = -kStiffness * (m_dm11 - target11) - kDamping * m_dmVel11; const float accel11 =
-kStiffness * (m_dm11 - target11) - kDamping * m_dmVel11;
m_dmVel11 += accel11 * dt; m_dmVel11 += accel11 * dt;
m_dm11 += m_dmVel11 * dt; m_dm11 += m_dmVel11 * dt;
m_deformMatrix = QMatrix4x4(m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); m_deformMatrix = QMatrix4x4(
m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
emit rawDeformMatrixChanged(); emit rawDeformMatrixChanged();
updateCenteredDeformMatrix(); updateCenteredDeformMatrix();
@@ -129,8 +131,7 @@ void BlobRect::setTopLeftRadius(qreal r) {
if (!qFuzzyCompare(m_topLeftRadius, r)) { if (!qFuzzyCompare(m_topLeftRadius, r)) {
m_topLeftRadius = r; m_topLeftRadius = r;
emit topLeftRadiusChanged(); emit topLeftRadiusChanged();
if (m_group) if (m_group) m_group->markDirty();
m_group->markDirty();
} }
} }
@@ -138,8 +139,7 @@ void BlobRect::setTopRightRadius(qreal r) {
if (!qFuzzyCompare(m_topRightRadius, r)) { if (!qFuzzyCompare(m_topRightRadius, r)) {
m_topRightRadius = r; m_topRightRadius = r;
emit topRightRadiusChanged(); emit topRightRadiusChanged();
if (m_group) if (m_group) m_group->markDirty();
m_group->markDirty();
} }
} }
@@ -147,8 +147,7 @@ void BlobRect::setBottomLeftRadius(qreal r) {
if (!qFuzzyCompare(m_bottomLeftRadius, r)) { if (!qFuzzyCompare(m_bottomLeftRadius, r)) {
m_bottomLeftRadius = r; m_bottomLeftRadius = r;
emit bottomLeftRadiusChanged(); emit bottomLeftRadiusChanged();
if (m_group) if (m_group) m_group->markDirty();
m_group->markDirty();
} }
} }
@@ -156,38 +155,51 @@ void BlobRect::setBottomRightRadius(qreal r) {
if (!qFuzzyCompare(m_bottomRightRadius, r)) { if (!qFuzzyCompare(m_bottomRightRadius, r)) {
m_bottomRightRadius = r; m_bottomRightRadius = r;
emit bottomRightRadiusChanged(); emit bottomRightRadiusChanged();
if (m_group) if (m_group) m_group->markDirty();
m_group->markDirty();
} }
} }
void BlobRect::cornerRadii(float out[4]) const { void BlobRect::cornerRadii(float out[4]) const {
const auto maxR = static_cast<float>(std::min(width(), height())) * 0.5f; const auto maxR = static_cast<float>(std::min(width(), height())) * 0.5f;
const auto base = std::min(static_cast<float>(m_radius), maxR); const auto base = std::min(static_cast<float>(m_radius), maxR);
out[0] = std::min(m_topRightRadius >= 0 ? static_cast<float>(m_topRightRadius) : base, maxR); out[0] = std::min(
out[1] = std::min(m_bottomRightRadius >= 0 ? static_cast<float>(m_bottomRightRadius) : base, maxR); m_topRightRadius >= 0 ? static_cast<float>(m_topRightRadius) : base,
out[2] = std::min(m_bottomLeftRadius >= 0 ? static_cast<float>(m_bottomLeftRadius) : base, maxR); maxR);
out[3] = std::min(m_topLeftRadius >= 0 ? static_cast<float>(m_topLeftRadius) : base, maxR); out[1] = std::min(
m_bottomRightRadius >= 0 ? static_cast<float>(m_bottomRightRadius)
: base,
maxR);
out[2] = std::min(
m_bottomLeftRadius >= 0 ? static_cast<float>(m_bottomLeftRadius) : base,
maxR);
out[3] = std::min(
m_topLeftRadius >= 0 ? static_cast<float>(m_topLeftRadius) : base,
maxR);
} }
bool BlobRect::isExcluded(const BlobShape* other) const { bool BlobRect::isExcluded(const BlobShape* other) const {
for (const auto& ptr : m_exclude) { for (const auto& ptr : m_exclude) {
if (ptr == other) if (ptr == other) return true;
return true;
} }
return false; return false;
} }
QQmlListProperty<BlobRect> BlobRect::exclude() { QQmlListProperty<BlobRect> BlobRect::exclude() {
return QQmlListProperty<BlobRect>( return QQmlListProperty<BlobRect>(
this, nullptr, &excludeAppend, &excludeCount, &excludeAt, &excludeClear, &excludeReplace, &excludeRemoveLast); this,
nullptr,
&excludeAppend,
&excludeCount,
&excludeAt,
&excludeClear,
&excludeReplace,
&excludeRemoveLast);
} }
void BlobRect::excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) { void BlobRect::excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
auto* self = static_cast<BlobRect*>(prop->object); auto* self = static_cast<BlobRect*>(prop->object);
self->m_exclude.append(rect); self->m_exclude.append(rect);
if (self->m_group) if (self->m_group) self->m_group->markDirty();
self->m_group->markDirty();
emit self->excludeChanged(); emit self->excludeChanged();
} }
@@ -196,44 +208,43 @@ qsizetype BlobRect::excludeCount(QQmlListProperty<BlobRect>* prop) {
return self->m_exclude.size(); return self->m_exclude.size();
} }
BlobRect* BlobRect::excludeAt(QQmlListProperty<BlobRect>* prop, qsizetype index) { BlobRect* BlobRect::excludeAt(
QQmlListProperty<BlobRect>* prop, qsizetype index) {
auto* self = static_cast<BlobRect*>(prop->object); auto* self = static_cast<BlobRect*>(prop->object);
return self->m_exclude.at(index); return self->m_exclude.at(index);
} }
void BlobRect::excludeClear(QQmlListProperty<BlobRect>* prop) { void BlobRect::excludeClear(QQmlListProperty<BlobRect>* prop) {
auto* self = static_cast<BlobRect*>(prop->object); auto* self = static_cast<BlobRect*>(prop->object);
if (self->m_exclude.isEmpty()) if (self->m_exclude.isEmpty()) return;
return;
self->m_exclude.clear(); self->m_exclude.clear();
if (self->m_group) if (self->m_group) self->m_group->markDirty();
self->m_group->markDirty();
emit self->excludeChanged(); emit self->excludeChanged();
} }
void BlobRect::excludeReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect) { void BlobRect::excludeReplace(
QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect) {
auto* self = static_cast<BlobRect*>(prop->object); auto* self = static_cast<BlobRect*>(prop->object);
self->m_exclude[index] = rect; self->m_exclude[index] = rect;
if (self->m_group) if (self->m_group) self->m_group->markDirty();
self->m_group->markDirty();
emit self->excludeChanged(); emit self->excludeChanged();
} }
void BlobRect::excludeRemoveLast(QQmlListProperty<BlobRect>* prop) { void BlobRect::excludeRemoveLast(QQmlListProperty<BlobRect>* prop) {
auto* self = static_cast<BlobRect*>(prop->object); auto* self = static_cast<BlobRect*>(prop->object);
if (self->m_exclude.isEmpty()) if (self->m_exclude.isEmpty()) return;
return;
self->m_exclude.removeLast(); self->m_exclude.removeLast();
if (self->m_group) if (self->m_group) self->m_group->markDirty();
self->m_group->markDirty();
emit self->excludeChanged(); emit self->excludeChanged();
} }
void BlobRect::checkAtRest(float speed) { void BlobRect::checkAtRest(float speed) {
constexpr float kEpsilon = 0.002f; constexpr float kEpsilon = 0.002f;
const bool atRest = std::abs(m_dm00 - 1.0f) < kEpsilon && std::abs(m_dm01) < kEpsilon && const bool atRest =
std::abs(m_dm11 - 1.0f) < kEpsilon && std::abs(m_dmVel00) < kEpsilon && std::abs(m_dm00 - 1.0f) < kEpsilon && std::abs(m_dm01) < kEpsilon &&
std::abs(m_dmVel01) < kEpsilon && std::abs(m_dmVel11) < kEpsilon && speed < 5.0f; std::abs(m_dm11 - 1.0f) < kEpsilon && std::abs(m_dmVel00) < kEpsilon &&
std::abs(m_dmVel01) < kEpsilon && std::abs(m_dmVel11) < kEpsilon &&
speed < 5.0f;
if (atRest) { if (atRest) {
m_dm00 = 1.0f; m_dm00 = 1.0f;
@@ -251,9 +262,8 @@ void BlobRect::checkAtRest(float speed) {
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
this, this,
[this]() { [this]() {
if (m_group) if (m_group) m_group->markDirty();
m_group->markDirty(); },
},
Qt::QueuedConnection); Qt::QueuedConnection);
} }
} }
+101 -99
View File
@@ -8,133 +8,135 @@
#include <qqmllist.h> #include <qqmllist.h>
class BlobRect : public BlobShape { class BlobRect : public BlobShape {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(qreal stiffness READ stiffness WRITE setStiffness NOTIFY stiffnessChanged) Q_PROPERTY(
Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged) qreal stiffness READ stiffness WRITE setStiffness NOTIFY
Q_PROPERTY(qreal deformScale READ deformScale WRITE setDeformScale NOTIFY deformScaleChanged) stiffnessChanged)
Q_PROPERTY(QQmlListProperty<BlobRect> exclude READ exclude NOTIFY excludeChanged) Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged)
Q_PROPERTY(qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY topLeftRadiusChanged) Q_PROPERTY(
Q_PROPERTY(qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY topRightRadiusChanged) qreal deformScale READ deformScale WRITE setDeformScale NOTIFY
Q_PROPERTY(qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius NOTIFY bottomLeftRadiusChanged) deformScaleChanged)
Q_PROPERTY( Q_PROPERTY(
qreal bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius NOTIFY bottomRightRadiusChanged) QQmlListProperty<BlobRect> exclude READ exclude NOTIFY excludeChanged)
Q_PROPERTY(
qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY
topLeftRadiusChanged)
Q_PROPERTY(
qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY
topRightRadiusChanged)
Q_PROPERTY(
qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius
NOTIFY bottomLeftRadiusChanged)
Q_PROPERTY(
qreal bottomRightRadius READ bottomRightRadius WRITE
setBottomRightRadius NOTIFY bottomRightRadiusChanged)
public: public:
explicit BlobRect(QQuickItem* parent = nullptr); explicit BlobRect(QQuickItem* parent = nullptr);
~BlobRect() override; ~BlobRect() override;
[[nodiscard]] qreal stiffness() const { [[nodiscard]] qreal stiffness() const { return m_stiffness; }
return m_stiffness;
}
void setStiffness(qreal s) { void setStiffness(qreal s) {
if (!qFuzzyCompare(m_stiffness, s)) { if (!qFuzzyCompare(m_stiffness, s)) {
m_stiffness = s; m_stiffness = s;
emit stiffnessChanged(); emit stiffnessChanged();
}
} }
}
[[nodiscard]] qreal damping() const { [[nodiscard]] qreal damping() const { return m_damping; }
return m_damping;
}
void setDamping(qreal d) { void setDamping(qreal d) {
if (!qFuzzyCompare(m_damping, d)) { if (!qFuzzyCompare(m_damping, d)) {
m_damping = d; m_damping = d;
emit dampingChanged(); emit dampingChanged();
}
} }
}
[[nodiscard]] qreal deformScale() const { [[nodiscard]] qreal deformScale() const { return m_deformScale; }
return m_deformScale;
}
void setDeformScale(qreal s) { void setDeformScale(qreal s) {
if (!qFuzzyCompare(m_deformScale, s)) { if (!qFuzzyCompare(m_deformScale, s)) {
m_deformScale = s; m_deformScale = s;
emit deformScaleChanged(); emit deformScaleChanged();
}
} }
}
QQmlListProperty<BlobRect> exclude(); QQmlListProperty<BlobRect> exclude();
bool isExcluded(const BlobShape* other) const override; bool isExcluded(const BlobShape* other) const override;
void cornerRadii(float out[4]) const override; void cornerRadii(float out[4]) const override;
[[nodiscard]] qreal topLeftRadius() const { [[nodiscard]] qreal topLeftRadius() const { return m_topLeftRadius; }
return m_topLeftRadius;
}
void setTopLeftRadius(qreal r); void setTopLeftRadius(qreal r);
[[nodiscard]] qreal topRightRadius() const { [[nodiscard]] qreal topRightRadius() const { return m_topRightRadius; }
return m_topRightRadius;
}
void setTopRightRadius(qreal r); void setTopRightRadius(qreal r);
[[nodiscard]] qreal bottomLeftRadius() const { [[nodiscard]] qreal bottomLeftRadius() const { return m_bottomLeftRadius; }
return m_bottomLeftRadius;
}
void setBottomLeftRadius(qreal r); void setBottomLeftRadius(qreal r);
[[nodiscard]] qreal bottomRightRadius() const { [[nodiscard]] qreal bottomRightRadius() const {
return m_bottomRightRadius; return m_bottomRightRadius;
} }
void setBottomRightRadius(qreal r); void setBottomRightRadius(qreal r);
signals: signals:
void stiffnessChanged(); void stiffnessChanged();
void dampingChanged(); void dampingChanged();
void deformScaleChanged(); void deformScaleChanged();
void excludeChanged(); void excludeChanged();
void topLeftRadiusChanged(); void topLeftRadiusChanged();
void topRightRadiusChanged(); void topRightRadiusChanged();
void bottomLeftRadiusChanged(); void bottomLeftRadiusChanged();
void bottomRightRadiusChanged(); void bottomRightRadiusChanged();
protected: protected:
void updatePolish() override; void updatePolish() override;
void updatePhysics() override; void updatePhysics() override;
private: private:
void checkAtRest(float speed); void checkAtRest(float speed);
// Physics state // Physics state
QPointF m_prevScenePos; QPointF m_prevScenePos;
QElapsedTimer m_elapsed; QElapsedTimer m_elapsed;
bool m_physicsActive = false; bool m_physicsActive = false;
bool m_hasPrevPos = false; bool m_hasPrevPos = false;
// Symmetric 2x2 deformation matrix components (3 independent: m00, m01, // Symmetric 2x2 deformation matrix components (3 independent: m00, m01,
// m11) Rest state is identity: m00=1, m01=0, m11=1 // m11) Rest state is identity: m00=1, m01=0, m11=1
float m_dm00 = 1.0f; float m_dm00 = 1.0f;
float m_dm01 = 0.0f; float m_dm01 = 0.0f;
float m_dm11 = 1.0f; float m_dm11 = 1.0f;
// Spring velocities for each component // Spring velocities for each component
float m_dmVel00 = 0.0f; float m_dmVel00 = 0.0f;
float m_dmVel01 = 0.0f; float m_dmVel01 = 0.0f;
float m_dmVel11 = 0.0f; float m_dmVel11 = 0.0f;
qreal m_stiffness = 200.0; qreal m_stiffness = 200.0;
qreal m_damping = 16.0; qreal m_damping = 16.0;
qreal m_deformScale = 0.0005; qreal m_deformScale = 0.0005;
qreal m_topLeftRadius = -1; qreal m_topLeftRadius = -1;
qreal m_topRightRadius = -1; qreal m_topRightRadius = -1;
qreal m_bottomLeftRadius = -1; qreal m_bottomLeftRadius = -1;
qreal m_bottomRightRadius = -1; qreal m_bottomRightRadius = -1;
QList<QPointer<BlobRect> > m_exclude; QList<QPointer<BlobRect>> m_exclude;
static void excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect); static void excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
static qsizetype excludeCount(QQmlListProperty<BlobRect>* prop); static qsizetype excludeCount(QQmlListProperty<BlobRect>* prop);
static BlobRect* excludeAt(QQmlListProperty<BlobRect>* prop, qsizetype index); static BlobRect* excludeAt(
static void excludeClear(QQmlListProperty<BlobRect>* prop); QQmlListProperty<BlobRect>* prop, qsizetype index);
static void excludeReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect); static void excludeClear(QQmlListProperty<BlobRect>* prop);
static void excludeRemoveLast(QQmlListProperty<BlobRect>* prop); static void excludeReplace(
QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
static void excludeRemoveLast(QQmlListProperty<BlobRect>* prop);
}; };
+132 -74
View File
@@ -18,7 +18,8 @@ static float deformPadding(const QMatrix4x4& dm, float hw, float hh) {
return std::max(extraX, extraY); return std::max(extraX, extraY);
} }
static float cpuSdBox(float px, float py, float cx, float cy, float hw, float hh) { static float cpuSdBox(
float px, float py, float cx, float cy, float hw, float hh) {
const float dx = std::abs(px - cx) - hw; const float dx = std::abs(px - cx) - hw;
const float dy = std::abs(py - cy) - hh; const float dy = std::abs(py - cy) - hh;
const float mdx = std::max(dx, 0.0f); const float mdx = std::max(dx, 0.0f);
@@ -31,54 +32,53 @@ static float cpuSmoothstep(float edge0, float edge1, float x) {
return t * t * (3.0f - 2.0f * t); return t * t * (3.0f - 2.0f * t);
} }
BlobShape::BlobShape(QQuickItem* parent) BlobShape::BlobShape(QQuickItem* parent) : QQuickItem(parent) {
: QQuickItem(parent) {
setFlag(ItemHasContents); setFlag(ItemHasContents);
} }
void BlobShape::setGroup(BlobGroup* g) { void BlobShape::setGroup(BlobGroup* g) {
if (m_group == g) if (m_group == g) return;
return; if (m_group && isComponentComplete()) unregisterFromGroup();
if (m_group && isComponentComplete())
unregisterFromGroup();
m_group = g; m_group = g;
if (m_group && isComponentComplete()) if (m_group && isComponentComplete()) registerWithGroup();
registerWithGroup();
emit groupChanged(); emit groupChanged();
if (m_group) if (m_group) m_group->markDirty();
m_group->markDirty();
} }
void BlobShape::setRadius(qreal r) { void BlobShape::setRadius(qreal r) {
if (qFuzzyCompare(m_radius, r)) if (qFuzzyCompare(m_radius, r)) return;
return;
m_radius = r; m_radius = r;
emit radiusChanged(); emit radiusChanged();
if (m_group) if (m_group) m_group->markDirty();
m_group->markDirty();
} }
void BlobShape::componentComplete() { void BlobShape::componentComplete() {
QQuickItem::componentComplete(); QQuickItem::componentComplete();
if (m_group) if (m_group) registerWithGroup();
registerWithGroup();
} }
void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) { void BlobShape::geometryChange(
const QRectF& newGeometry, const QRectF& oldGeometry) {
QQuickItem::geometryChange(newGeometry, oldGeometry); QQuickItem::geometryChange(newGeometry, oldGeometry);
updateCenteredDeformMatrix(); updateCenteredDeformMatrix();
if (m_group) { if (m_group) {
m_pendingDx += static_cast<float>(newGeometry.x() - oldGeometry.x()); m_pendingDx += static_cast<float>(newGeometry.x() - oldGeometry.x());
m_pendingDy += static_cast<float>(newGeometry.y() - oldGeometry.y()); m_pendingDy += static_cast<float>(newGeometry.y() - oldGeometry.y());
m_pendingDw += static_cast<float>(newGeometry.width() - oldGeometry.width()); m_pendingDw +=
m_pendingDh += static_cast<float>(newGeometry.height() - oldGeometry.height()); static_cast<float>(newGeometry.width() - oldGeometry.width());
m_pendingDh +=
static_cast<float>(newGeometry.height() - oldGeometry.height());
const float deformMag = std::abs(m_deformMatrix(0, 0) - 1.0f) + std::abs(m_deformMatrix(0, 1)) + const float deformMag = std::abs(m_deformMatrix(0, 0) - 1.0f) +
std::abs(m_deformMatrix(1, 0)) + std::abs(m_deformMatrix(1, 1) - 1.0f); std::abs(m_deformMatrix(0, 1)) +
std::abs(m_deformMatrix(1, 0)) +
std::abs(m_deformMatrix(1, 1) - 1.0f);
const float syncThreshold = deformMag > 0.001f ? 0.05f : 0.5f; const float syncThreshold = deformMag > 0.001f ? 0.05f : 0.5f;
if (std::abs(m_pendingDx) > syncThreshold || std::abs(m_pendingDy) > syncThreshold || if (std::abs(m_pendingDx) > syncThreshold ||
std::abs(m_pendingDw) > syncThreshold || std::abs(m_pendingDh) > syncThreshold) { std::abs(m_pendingDy) > syncThreshold ||
std::abs(m_pendingDw) > syncThreshold ||
std::abs(m_pendingDh) > syncThreshold) {
m_pendingDx = 0; m_pendingDx = 0;
m_pendingDy = 0; m_pendingDy = 0;
m_pendingDw = 0; m_pendingDw = 0;
@@ -111,18 +111,15 @@ void BlobShape::cornerRadii(float out[4]) const {
} }
void BlobShape::registerWithGroup() { void BlobShape::registerWithGroup() {
if (m_group) if (m_group) m_group->addShape(this);
m_group->addShape(this);
} }
void BlobShape::unregisterFromGroup() { void BlobShape::unregisterFromGroup() {
if (m_group) if (m_group) m_group->removeShape(this);
m_group->removeShape(this);
} }
void BlobShape::updatePolish() { void BlobShape::updatePolish() {
if (!m_group) if (!m_group) return;
return;
m_group->ensurePhysicsUpdated(); m_group->ensurePhysicsUpdated();
@@ -144,27 +141,30 @@ void BlobShape::updatePolish() {
m_cachedPaddedY = static_cast<float>(scenePos.y()) - totalPad; m_cachedPaddedY = static_cast<float>(scenePos.y()) - totalPad;
m_cachedPaddedW = static_cast<float>(width()) + 2.0f * totalPad; m_cachedPaddedW = static_cast<float>(width()) + 2.0f * totalPad;
m_cachedPaddedH = static_cast<float>(height()) + 2.0f * totalPad; m_cachedPaddedH = static_cast<float>(height()) + 2.0f * totalPad;
m_localPaddedRect = QRectF(static_cast<double>(-totalPad), static_cast<double>(-totalPad), m_localPaddedRect = QRectF(
width() + 2.0 * static_cast<double>(totalPad), height() + 2.0 * static_cast<double>(totalPad)); static_cast<double>(-totalPad),
static_cast<double>(-totalPad),
width() + 2.0 * static_cast<double>(totalPad),
height() + 2.0 * static_cast<double>(totalPad));
} }
m_cachedRects.clear(); m_cachedRects.clear();
m_cachedMyIndex = -2; m_cachedMyIndex = -2;
const QRectF myPadded(static_cast<double>(m_cachedPaddedX), static_cast<double>(m_cachedPaddedY), const QRectF myPadded(
static_cast<double>(m_cachedPaddedW), static_cast<double>(m_cachedPaddedH)); static_cast<double>(m_cachedPaddedX),
static_cast<double>(m_cachedPaddedY),
static_cast<double>(m_cachedPaddedW),
static_cast<double>(m_cachedPaddedH));
QVector<BlobShape*> rectShapes; QVector<BlobShape*> rectShapes;
rectShapes.reserve(m_group->shapes().size()); rectShapes.reserve(m_group->shapes().size());
for (BlobShape* other : m_group->shapes()) { for (BlobShape* other : m_group->shapes()) {
if (other->isInvertedRect()) if (other->isInvertedRect()) continue;
continue;
if (other->width() <= 0 || other->height() <= 0) if (other->width() <= 0 || other->height() <= 0) continue;
continue;
if (isExcluded(other)) if (isExcluded(other)) continue;
continue;
const QPointF otherScene = other->mapToScene(QPointF(0, 0)); const QPointF otherScene = other->mapToScene(QPointF(0, 0));
@@ -174,10 +174,13 @@ void BlobShape::updatePolish() {
} else { } else {
const float otherHW = static_cast<float>(other->width()) * 0.5f; const float otherHW = static_cast<float>(other->width()) * 0.5f;
const float otherHH = static_cast<float>(other->height()) * 0.5f; const float otherHH = static_cast<float>(other->height()) * 0.5f;
const float otherPad = pad + deformPadding(other->m_deformMatrix, otherHW, otherHH); const float otherPad =
const QRectF otherPadded(otherScene.x() - static_cast<double>(otherPad), pad + deformPadding(other->m_deformMatrix, otherHW, otherHH);
otherScene.y() - static_cast<double>(otherPad), other->width() + 2.0 * static_cast<double>(otherPad), const QRectF otherPadded(
other->height() + 2.0 * static_cast<double>(otherPad)); otherScene.x() - static_cast<double>(otherPad),
otherScene.y() - static_cast<double>(otherPad),
other->width() + 2.0 * static_cast<double>(otherPad),
other->height() + 2.0 * static_cast<double>(otherPad));
include = myPadded.intersects(otherPadded); include = myPadded.intersects(otherPadded);
} }
@@ -217,19 +220,16 @@ void BlobShape::updatePolish() {
} }
} }
if (isInvertedRect()) if (isInvertedRect()) m_cachedMyIndex = -1;
m_cachedMyIndex = -1;
const auto cachedCount = m_cachedRects.size(); const auto cachedCount = m_cachedRects.size();
for (qsizetype i = 0; i < cachedCount; ++i) { for (qsizetype i = 0; i < cachedCount; ++i) {
int mask = 0; int mask = 0;
BlobShape* si = rectShapes[i]; BlobShape* si = rectShapes[i];
for (qsizetype j = 0; j < cachedCount; ++j) { for (qsizetype j = 0; j < cachedCount; ++j) {
if (j == i) if (j == i) continue;
continue;
BlobShape* sj = rectShapes[j]; BlobShape* sj = rectShapes[j];
if (si->isExcluded(sj) || sj->isExcluded(si)) if (si->isExcluded(sj) || sj->isExcluded(si)) mask |= (1 << j);
mask |= (1 << j);
} }
m_cachedRects[i].excludeMask = mask; m_cachedRects[i].excludeMask = mask;
} }
@@ -242,15 +242,25 @@ void BlobShape::updatePolish() {
auto* inv = m_group->invertedRect(); auto* inv = m_group->invertedRect();
if (inv) { if (inv) {
const QPointF invScene = inv->mapToScene(QPointF(0, 0)); const QPointF invScene = inv->mapToScene(QPointF(0, 0));
const float outerCX = static_cast<float>(invScene.x() + inv->width() / 2.0); const float outerCX =
const float outerCY = static_cast<float>(invScene.y() + inv->height() / 2.0); static_cast<float>(invScene.x() + inv->width() / 2.0);
const float outerCY =
static_cast<float>(invScene.y() + inv->height() / 2.0);
const float outerHW = static_cast<float>(inv->width() / 2.0); const float outerHW = static_cast<float>(inv->width() / 2.0);
const float outerHH = static_cast<float>(inv->height() / 2.0); const float outerHH = static_cast<float>(inv->height() / 2.0);
const float innerCX = outerCX + static_cast<float>((inv->borderLeft() - inv->borderRight()) / 2.0); const float innerCX =
const float innerCY = outerCY + static_cast<float>((inv->borderTop() - inv->borderBottom()) / 2.0); outerCX +
const float innerHW = outerHW - static_cast<float>((inv->borderLeft() + inv->borderRight()) / 2.0); static_cast<float>((inv->borderLeft() - inv->borderRight()) / 2.0);
const float innerHH = outerHH - static_cast<float>((inv->borderTop() + inv->borderBottom()) / 2.0); const float innerCY =
outerCY +
static_cast<float>((inv->borderTop() - inv->borderBottom()) / 2.0);
const float innerHW =
outerHW -
static_cast<float>((inv->borderLeft() + inv->borderRight()) / 2.0);
const float innerHH =
outerHH -
static_cast<float>((inv->borderTop() + inv->borderBottom()) / 2.0);
bool nearBorder = isInvertedRect(); bool nearBorder = isInvertedRect();
if (!nearBorder) { if (!nearBorder) {
@@ -259,8 +269,10 @@ void BlobShape::updatePolish() {
const float myCY = m_cachedPaddedY + m_cachedPaddedH * 0.5f; const float myCY = m_cachedPaddedY + m_cachedPaddedH * 0.5f;
const float myHW = m_cachedPaddedW * 0.5f; const float myHW = m_cachedPaddedW * 0.5f;
const float myHH = m_cachedPaddedH * 0.5f; const float myHH = m_cachedPaddedH * 0.5f;
nearBorder = (myCX - myHW < innerCX - innerHW + margin) || (myCX + myHW > innerCX + innerHW - margin) || nearBorder = (myCX - myHW < innerCX - innerHW + margin) ||
(myCY - myHH < innerCY - innerHH + margin) || (myCY + myHH > innerCY + innerHH - margin); (myCX + myHW > innerCX + innerHW - margin) ||
(myCY - myHH < innerCY - innerHH + margin) ||
(myCY + myHH > innerCY + innerHH - margin);
} }
if (nearBorder) { if (nearBorder) {
@@ -293,15 +305,33 @@ void BlobShape::updatePolish() {
const float cTlX = ri.cx - ri.hw, cTlY = ri.cy - ri.hh; const float cTlX = ri.cx - ri.hw, cTlY = ri.cy - ri.hh;
for (qsizetype j = 0; j < rectCount; ++j) { for (qsizetype j = 0; j < rectCount; ++j) {
if (j == i) if (j == i) continue;
continue; if (riExcludeMask & (1 << j)) continue;
if (riExcludeMask & (1 << j))
continue;
const auto& rj = m_cachedRects[j]; const auto& rj = m_cachedRects[j];
fTr = std::min(fTr, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh))); fTr = std::min(
fBr = std::min(fBr, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh))); fTr,
fBl = std::min(fBl, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cBlX, cBlY, rj.cx, rj.cy, rj.hw, rj.hh))); cpuSmoothstep(
fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cTlX, cTlY, rj.cx, rj.cy, rj.hw, rj.hh))); 0.0f,
smoothFactor,
cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh)));
fBr = std::min(
fBr,
cpuSmoothstep(
0.0f,
smoothFactor,
cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh)));
fBl = std::min(
fBl,
cpuSmoothstep(
0.0f,
smoothFactor,
cpuSdBox(cBlX, cBlY, rj.cx, rj.cy, rj.hw, rj.hh)));
fTl = std::min(
fTl,
cpuSmoothstep(
0.0f,
smoothFactor,
cpuSdBox(cTlX, cTlY, rj.cx, rj.cy, rj.hw, rj.hh)));
} }
if (m_cachedHasInverted) { if (m_cachedHasInverted) {
@@ -309,10 +339,30 @@ void BlobShape::updatePolish() {
const float icy = m_cachedInvertedInner[1]; const float icy = m_cachedInvertedInner[1];
const float ihw = m_cachedInvertedInner[2]; const float ihw = m_cachedInvertedInner[2];
const float ihh = m_cachedInvertedInner[3]; const float ihh = m_cachedInvertedInner[3];
fTr = std::min(fTr, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cTrX, cTrY, icx, icy, ihw, ihh))); fTr = std::min(
fBr = std::min(fBr, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cBrX, cBrY, icx, icy, ihw, ihh))); fTr,
fBl = std::min(fBl, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cBlX, cBlY, icx, icy, ihw, ihh))); cpuSmoothstep(
fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh))); 0.0f,
smoothFactor,
-cpuSdBox(cTrX, cTrY, icx, icy, ihw, ihh)));
fBr = std::min(
fBr,
cpuSmoothstep(
0.0f,
smoothFactor,
-cpuSdBox(cBrX, cBrY, icx, icy, ihw, ihh)));
fBl = std::min(
fBl,
cpuSmoothstep(
0.0f,
smoothFactor,
-cpuSdBox(cBlX, cBlY, icx, icy, ihw, ihh)));
fTl = std::min(
fTl,
cpuSmoothstep(
0.0f,
smoothFactor,
-cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh)));
} }
ri.radius[0] = std::max(ri.radius[0] * fTr, minR); ri.radius[0] = std::max(ri.radius[0] * fTr, minR);
@@ -332,7 +382,8 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
if (!node) { if (!node) {
node = new QSGGeometryNode; node = new QSGGeometryNode;
auto* geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4); auto* geometry =
new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
geometry->setDrawingMode(QSGGeometry::DrawTriangleStrip); geometry->setDrawingMode(QSGGeometry::DrawTriangleStrip);
node->setGeometry(geometry); node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry); node->setFlag(QSGNode::OwnsGeometry);
@@ -368,10 +419,17 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
material->m_color = m_group->color(); material->m_color = m_group->color();
material->m_hasInverted = m_cachedHasInverted ? 1 : 0; material->m_hasInverted = m_cachedHasInverted ? 1 : 0;
material->m_invertedRadius = m_cachedInvertedRadius; material->m_invertedRadius = m_cachedInvertedRadius;
memcpy(material->m_invertedOuter, m_cachedInvertedOuter, sizeof(m_cachedInvertedOuter)); memcpy(
memcpy(material->m_invertedInner, m_cachedInvertedInner, sizeof(m_cachedInvertedInner)); material->m_invertedOuter,
m_cachedInvertedOuter,
sizeof(m_cachedInvertedOuter));
memcpy(
material->m_invertedInner,
m_cachedInvertedInner,
sizeof(m_cachedInvertedInner));
const int count = static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16))); const int count =
static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16)));
material->m_rectCount = count; material->m_rectCount = count;
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
material->m_rects[i] = m_cachedRects[i]; material->m_rects[i] = m_cachedRects[i];
+57 -64
View File
@@ -9,85 +9,78 @@
class BlobGroup; class BlobGroup;
class BlobShape : public QQuickItem { class BlobShape : public QQuickItem {
Q_OBJECT Q_OBJECT
Q_PROPERTY(BlobGroup* group READ group WRITE setGroup NOTIFY groupChanged) Q_PROPERTY(BlobGroup* group READ group WRITE setGroup NOTIFY groupChanged)
Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged) Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged)
Q_PROPERTY(QMatrix4x4 deformMatrix READ deformMatrix NOTIFY deformMatrixChanged) Q_PROPERTY(
Q_PROPERTY(QMatrix4x4 rawDeformMatrix READ rawDeformMatrix NOTIFY rawDeformMatrixChanged) QMatrix4x4 deformMatrix READ deformMatrix NOTIFY deformMatrixChanged)
Q_PROPERTY(
QMatrix4x4 rawDeformMatrix READ rawDeformMatrix NOTIFY
rawDeformMatrixChanged)
friend class BlobGroup; friend class BlobGroup;
public: public:
explicit BlobShape(QQuickItem* parent = nullptr); explicit BlobShape(QQuickItem* parent = nullptr);
~BlobShape() override = default; ~BlobShape() override = default;
[[nodiscard]] BlobGroup* group() const { [[nodiscard]] BlobGroup* group() const { return m_group; }
return m_group;
}
void setGroup(BlobGroup* g); void setGroup(BlobGroup* g);
[[nodiscard]] qreal radius() const { [[nodiscard]] qreal radius() const { return m_radius; }
return m_radius;
}
void setRadius(qreal r); void setRadius(qreal r);
[[nodiscard]] QMatrix4x4 deformMatrix() const { [[nodiscard]] QMatrix4x4 deformMatrix() const {
return m_centeredDeformMatrix; return m_centeredDeformMatrix;
} }
[[nodiscard]] QMatrix4x4 rawDeformMatrix() const { [[nodiscard]] QMatrix4x4 rawDeformMatrix() const { return m_deformMatrix; }
return m_deformMatrix;
}
signals: signals:
void groupChanged(); void groupChanged();
void radiusChanged(); void radiusChanged();
void deformMatrixChanged(); void deformMatrixChanged();
void rawDeformMatrixChanged(); void rawDeformMatrixChanged();
protected: protected:
void componentComplete() override; void componentComplete() override;
void geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) override; void geometryChange(
void updatePolish() override; const QRectF& newGeometry, const QRectF& oldGeometry) override;
QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override; void updatePolish() override;
QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override;
[[nodiscard]] virtual bool isInvertedRect() const { [[nodiscard]] virtual bool isInvertedRect() const { return false; }
return false;
}
virtual bool isExcluded(const BlobShape* /*other*/) const { virtual bool isExcluded(const BlobShape*) const { return false; }
return false;
}
virtual void cornerRadii(float out[4]) const; virtual void cornerRadii(float out[4]) const;
virtual void updatePhysics() { virtual void updatePhysics() {}
}
virtual void registerWithGroup(); virtual void registerWithGroup();
virtual void unregisterFromGroup(); virtual void unregisterFromGroup();
void updateCenteredDeformMatrix(); void updateCenteredDeformMatrix();
BlobGroup* m_group = nullptr; BlobGroup* m_group = nullptr;
qreal m_radius = 0; qreal m_radius = 0;
QMatrix4x4 m_deformMatrix; QMatrix4x4 m_deformMatrix;
QMatrix4x4 m_centeredDeformMatrix; QMatrix4x4 m_centeredDeformMatrix;
float m_cachedPaddedX = 0; float m_cachedPaddedX = 0;
float m_cachedPaddedY = 0; float m_cachedPaddedY = 0;
float m_cachedPaddedW = 0; float m_cachedPaddedW = 0;
float m_cachedPaddedH = 0; float m_cachedPaddedH = 0;
QRectF m_localPaddedRect; QRectF m_localPaddedRect;
QVector<BlobRectData> m_cachedRects; QVector<BlobRectData> m_cachedRects;
int m_cachedMyIndex = -2; int m_cachedMyIndex = -2;
float m_pendingDx = 0; float m_pendingDx = 0;
float m_pendingDy = 0; float m_pendingDy = 0;
float m_pendingDw = 0; float m_pendingDw = 0;
float m_pendingDh = 0; float m_pendingDh = 0;
bool m_cachedHasInverted = false; bool m_cachedHasInverted = false;
float m_cachedInvertedRadius = 0; float m_cachedInvertedRadius = 0;
float m_cachedInvertedOuter[4] = {}; float m_cachedInvertedOuter[4] = {};
float m_cachedInvertedInner[4] = {}; float m_cachedInvertedInner[4] = {};
}; };
+32 -30
View File
@@ -9,7 +9,7 @@ pkg_check_modules(Cava IMPORTED_TARGET libcava QUIET)
pkg_check_modules(GLIB REQUIRED glib-2.0 gobject-2.0 gio-2.0) pkg_check_modules(GLIB REQUIRED glib-2.0 gobject-2.0 gio-2.0)
if(NOT Cava_FOUND) if(NOT Cava_FOUND)
pkg_check_modules(Cava IMPORTED_TARGET cava REQUIRED) pkg_check_modules(Cava IMPORTED_TARGET cava REQUIRED)
endif() endif()
if(NOT TARGET Sensors::Sensors) if(NOT TARGET Sensors::Sensors)
@@ -24,48 +24,50 @@ set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml")
qt_standard_project_setup(REQUIRES 6.9) qt_standard_project_setup(REQUIRES 6.9)
function(qml_module arg_TARGET) function(qml_module arg_TARGET)
cmake_parse_arguments(PARSE_ARGV 1 arg "" "URI" "SOURCES;LIBRARIES") cmake_parse_arguments(PARSE_ARGV 1 arg "" "URI" "SOURCES;LIBRARIES")
qt_add_qml_module(${arg_TARGET} qt_add_qml_module(${arg_TARGET}
URI ${arg_URI} URI ${arg_URI}
VERSION 1.0 VERSION 1.0
SOURCES ${arg_SOURCES} SOURCES ${arg_SOURCES}
) )
qt_query_qml_module(${arg_TARGET} qt_query_qml_module(${arg_TARGET}
URI module_uri URI module_uri
VERSION module_version VERSION module_version
PLUGIN_TARGET module_plugin_target PLUGIN_TARGET module_plugin_target
TARGET_PATH module_target_path TARGET_PATH module_target_path
QMLDIR module_qmldir QMLDIR module_qmldir
TYPEINFO module_typeinfo TYPEINFO module_typeinfo
) )
set(module_dir "${INSTALL_QMLDIR}/${module_target_path}") set(module_dir "${INSTALL_QMLDIR}/${module_target_path}")
install(TARGETS ${arg_TARGET} LIBRARY DESTINATION "${module_dir}" RUNTIME DESTINATION "${module_dir}") install(TARGETS ${arg_TARGET} LIBRARY DESTINATION "${module_dir}" RUNTIME DESTINATION "${module_dir}")
install(TARGETS "${module_plugin_target}" LIBRARY DESTINATION "${module_dir}" RUNTIME DESTINATION "${module_dir}") install(TARGETS "${module_plugin_target}" LIBRARY DESTINATION "${module_dir}" RUNTIME DESTINATION "${module_dir}")
install(FILES "${module_qmldir}" DESTINATION "${module_dir}") install(FILES "${module_qmldir}" DESTINATION "${module_dir}")
install(FILES "${module_typeinfo}" DESTINATION "${module_dir}") install(FILES "${module_typeinfo}" DESTINATION "${module_dir}")
target_link_libraries(${arg_TARGET} PRIVATE Qt::Core Qt::Qml ${arg_LIBRARIES}) target_link_libraries(${arg_TARGET} PRIVATE Qt::Core Qt::Qml ${arg_LIBRARIES})
endfunction() endfunction()
qml_module(ZShell qml_module(ZShell
URI ZShell URI ZShell
SOURCES SOURCES
writefile.hpp writefile.cpp writefile.hpp writefile.cpp
appdb.hpp appdb.cpp appdb.hpp appdb.cpp
imageanalyser.hpp imageanalyser.cpp imageanalyser.hpp imageanalyser.cpp
requests.hpp requests.cpp requests.hpp requests.cpp
toaster.hpp toaster.cpp toaster.hpp toaster.cpp
qalculator.hpp qalculator.cpp qalculator.hpp qalculator.cpp
zutils.hpp zutils.cpp zutils.hpp zutils.cpp
LIBRARIES LIBRARIES
Qt::Gui Qt::Gui
Qt::Quick Qt::Quick
Qt::Concurrent Qt::Concurrent
Qt::Sql Qt::Sql
Qt::DBus Qt::DBus
PkgConfig::Qalculate PkgConfig::Qalculate
) )
target_compile_definitions(ZShell PRIVATE ZSHELL_VERSION="${VERSION}")
add_subdirectory(Models) add_subdirectory(Models)
add_subdirectory(Internal) add_subdirectory(Internal)
add_subdirectory(Services) add_subdirectory(Services)
+38 -24
View File
@@ -3,11 +3,10 @@
namespace ZShell::components { namespace ZShell::components {
ButtonRow::ButtonRow(QQuickItem* parent) ButtonRow::ButtonRow(QQuickItem* parent)
: QQuickItem(parent) : QQuickItem(parent), m_dirty(false), m_spacing(0.0) {
, m_dirty(false)
, m_spacing(0.0) {
setFlag(QQuickItem::ItemHasContents, true); setFlag(QQuickItem::ItemHasContents, true);
QObject::connect(this, &ButtonRow::widthChanged, this, &ButtonRow::invalidate); QObject::connect(
this, &ButtonRow::widthChanged, this, &ButtonRow::invalidate);
} }
qreal ButtonRow::spacing() const { qreal ButtonRow::spacing() const {
@@ -15,8 +14,7 @@ qreal ButtonRow::spacing() const {
} }
void ButtonRow::setSpacing(qreal spacing) { void ButtonRow::setSpacing(qreal spacing) {
if (qFuzzyCompare(m_spacing + 1.0, spacing + 1.0)) if (qFuzzyCompare(m_spacing + 1.0, spacing + 1.0)) return;
return;
m_spacing = spacing; m_spacing = spacing;
emit spacingChanged(); emit spacingChanged();
@@ -24,18 +22,33 @@ void ButtonRow::setSpacing(qreal spacing) {
invalidate(); invalidate();
} }
void ButtonRow::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& data) { void ButtonRow::itemChange(
QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& data) {
if (change == QQuickItem::ItemChildAddedChange) { if (change == QQuickItem::ItemChildAddedChange) {
auto* const child = data.item; auto* const child = data.item;
QObject::connect(child, &QQuickItem::implicitWidthChanged, this, &ButtonRow::invalidate); QObject::connect(
QObject::connect(child, &QQuickItem::implicitHeightChanged, this, &ButtonRow::invalidate); child,
QObject::connect(child, &QQuickItem::visibleChanged, this, &ButtonRow::invalidate); &QQuickItem::implicitWidthChanged,
this,
&ButtonRow::invalidate);
QObject::connect(
child,
&QQuickItem::implicitHeightChanged,
this,
&ButtonRow::invalidate);
QObject::connect(
child, &QQuickItem::visibleChanged, this, &ButtonRow::invalidate);
const auto* childMeta = child->metaObject(); const auto* childMeta = child->metaObject();
const auto morphSignalIdx = childMeta->indexOfSignal("shapeMorphExpansionChanged()"); const auto morphSignalIdx =
childMeta->indexOfSignal("shapeMorphExpansionChanged()");
if (morphSignalIdx != -1) if (morphSignalIdx != -1)
QObject::connect(child, childMeta->method(morphSignalIdx), this, QObject::connect(
metaObject()->method(metaObject()->indexOfSlot("invalidate()"))); child,
childMeta->method(morphSignalIdx),
this,
metaObject()->method(
metaObject()->indexOfSlot("invalidate()")));
invalidate(); invalidate();
} else if (change == QQuickItem::ItemChildRemovedChange) { } else if (change == QQuickItem::ItemChildRemovedChange) {
@@ -77,8 +90,7 @@ void ButtonRow::relayout() {
maxHeight = qMax(maxHeight, child->implicitHeight()); maxHeight = qMax(maxHeight, child->implicitHeight());
const auto prop = child->property("fillWidth"); const auto prop = child->property("fillWidth");
if (!prop.isValid()) if (!prop.isValid()) continue;
continue;
if (prop.toBool()) { if (prop.toBool()) {
fillWidthCount++; fillWidthCount++;
@@ -88,15 +100,17 @@ void ButtonRow::relayout() {
} }
} }
if (fillWidthCount == 0) if (fillWidthCount == 0) fillWidthCount = 1; // Avoid divide by 0
fillWidthCount = 1; // Avoid divide by 0
const auto widthPerItem = (width() - totalSpacing - reservedWidth) / static_cast<qreal>(fillWidthCount); const auto widthPerItem = (width() - totalSpacing - reservedWidth) /
static_cast<qreal>(fillWidthCount);
QList<qreal> baseWidths; QList<qreal> baseWidths;
baseWidths.reserve(nChildren); baseWidths.reserve(nChildren);
for (auto* const child : validChildren) for (auto* const child : validChildren)
baseWidths.append(child->property("fillWidth").toBool() ? widthPerItem : child->implicitWidth()); baseWidths.append(
child->property("fillWidth").toBool() ? widthPerItem
: child->implicitWidth());
qreal accX = 0; qreal accX = 0;
for (int i = 0; i < nChildren; ++i) { for (int i = 0; i < nChildren; ++i) {
@@ -108,12 +122,12 @@ void ButtonRow::relayout() {
// clang-format on // clang-format on
// Items at edges push by full amount, items in middle push by half // Items at edges push by full amount, items in middle push by half
if (i > 1) if (i > 1) prevExtraWidth /= 2;
prevExtraWidth /= 2; if (i < nChildren - 2) nextExtraWidth /= 2;
if (i < nChildren - 2)
nextExtraWidth /= 2;
child->setWidth(baseWidths[i] + getMorphExpansion(child) - prevExtraWidth - nextExtraWidth); child->setWidth(
baseWidths[i] + getMorphExpansion(child) - prevExtraWidth -
nextExtraWidth);
child->setHeight(maxHeight); child->setHeight(maxHeight);
child->setX(accX); child->setX(accX);
+22 -19
View File
@@ -5,33 +5,36 @@
namespace ZShell::components { namespace ZShell::components {
class ButtonRow : public QQuickItem { class ButtonRow : public QQuickItem {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged) Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
public: public:
explicit ButtonRow(QQuickItem* parent = nullptr); explicit ButtonRow(QQuickItem* parent = nullptr);
[[nodiscard]] qreal spacing() const; [[nodiscard]] qreal spacing() const;
void setSpacing(qreal spacing); void setSpacing(qreal spacing);
signals: signals:
void spacingChanged(); void spacingChanged();
protected: protected:
void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& data) override; void itemChange(
void updatePolish() override; QQuickItem::ItemChange change,
const QQuickItem::ItemChangeData& data) override;
void updatePolish() override;
private slots: private slots:
void invalidate(); void invalidate();
private: // NOLINTNEXTLINE(readability-redundant-access-specifiers)
void relayout(); private:
static qreal getMorphExpansion(const QQuickItem* item); void relayout();
static qreal getMorphExpansion(const QQuickItem* item);
bool m_dirty; bool m_dirty;
qreal m_spacing; qreal m_spacing;
}; };
} // namespace ZShell::components } // namespace ZShell::components
File diff suppressed because it is too large Load Diff
+210 -177
View File
@@ -12,232 +12,265 @@
namespace ZShell::components { namespace ZShell::components {
class LazyListViewAttached : public QObject { class LazyListViewAttached : public QObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(qreal preferredHeight READ preferredHeight WRITE setPreferredHeight NOTIFY preferredHeightChanged) Q_PROPERTY(
Q_PROPERTY(qreal visibleHeight READ visibleHeight WRITE setVisibleHeight NOTIFY visibleHeightChanged) qreal preferredHeight READ preferredHeight WRITE setPreferredHeight
Q_PROPERTY(bool ready READ ready NOTIFY readyChanged) NOTIFY preferredHeightChanged)
Q_PROPERTY(bool adding READ adding NOTIFY addingChanged) Q_PROPERTY(
Q_PROPERTY(bool removing READ removing NOTIFY removingChanged) qreal visibleHeight READ visibleHeight WRITE setVisibleHeight NOTIFY
Q_PROPERTY(bool trackViewport READ trackViewport WRITE setTrackViewport NOTIFY trackViewportChanged) visibleHeightChanged)
Q_PROPERTY(bool ready READ ready NOTIFY readyChanged)
Q_PROPERTY(bool adding READ adding NOTIFY addingChanged)
Q_PROPERTY(bool removing READ removing NOTIFY removingChanged)
Q_PROPERTY(
bool trackViewport READ trackViewport WRITE setTrackViewport NOTIFY
trackViewportChanged)
public: public:
explicit LazyListViewAttached(QObject* parent = nullptr); explicit LazyListViewAttached(QObject* parent = nullptr);
[[nodiscard]] qreal preferredHeight() const; [[nodiscard]] qreal preferredHeight() const;
void setPreferredHeight(qreal height); void setPreferredHeight(qreal height);
[[nodiscard]] qreal visibleHeight() const; [[nodiscard]] qreal visibleHeight() const;
void setVisibleHeight(qreal height); void setVisibleHeight(qreal height);
[[nodiscard]] bool ready() const; [[nodiscard]] bool ready() const;
void setReady(bool ready); void setReady(bool ready);
[[nodiscard]] bool adding() const; [[nodiscard]] bool adding() const;
void setAdding(bool adding); void setAdding(bool adding);
[[nodiscard]] bool removing() const; [[nodiscard]] bool removing() const;
void setRemoving(bool removing); void setRemoving(bool removing);
[[nodiscard]] bool trackViewport() const; [[nodiscard]] bool trackViewport() const;
void setTrackViewport(bool track); void setTrackViewport(bool track);
signals: signals:
void preferredHeightChanged(); void preferredHeightChanged();
void visibleHeightChanged(); void visibleHeightChanged();
void readyChanged(); void readyChanged();
void addingChanged(); void addingChanged();
void removingChanged(); void removingChanged();
void trackViewportChanged(); void trackViewportChanged();
private: private:
qreal m_preferredHeight = -1; qreal m_preferredHeight = -1;
qreal m_visibleHeight = -1; qreal m_visibleHeight = -1;
bool m_ready = false; bool m_ready = false;
bool m_adding = false; bool m_adding = false;
bool m_removing = false; bool m_removing = false;
bool m_trackViewport = false; bool m_trackViewport = false;
}; };
class LazyListView : public QQuickItem { class LazyListView : public QQuickItem {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_ATTACHED(LazyListViewAttached) QML_ATTACHED(LazyListViewAttached)
// Model & Delegate // Model & Delegate
Q_PROPERTY(QAbstractItemModel* model READ model WRITE setModel NOTIFY modelChanged) Q_PROPERTY(
Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) QAbstractItemModel* model READ model WRITE setModel NOTIFY modelChanged)
Q_PROPERTY(
QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY
delegateChanged)
// Layout // Layout
Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged) Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
Q_PROPERTY(qreal contentHeight READ contentHeight NOTIFY contentHeightChanged) Q_PROPERTY(
Q_PROPERTY(qreal layoutHeight READ layoutHeight NOTIFY layoutHeightChanged) qreal contentHeight READ contentHeight NOTIFY contentHeightChanged)
Q_PROPERTY(qreal contentY READ contentY WRITE setContentY NOTIFY contentYChanged) Q_PROPERTY(qreal layoutHeight READ layoutHeight NOTIFY layoutHeightChanged)
Q_PROPERTY(
qreal contentY READ contentY WRITE setContentY NOTIFY contentYChanged)
// Viewport & Lazy Loading // Viewport & Lazy Loading
Q_PROPERTY(QRectF viewport READ viewport WRITE setViewport NOTIFY viewportChanged) Q_PROPERTY(
Q_PROPERTY(bool useCustomViewport READ useCustomViewport WRITE setUseCustomViewport NOTIFY useCustomViewportChanged) QRectF viewport READ viewport WRITE setViewport NOTIFY viewportChanged)
Q_PROPERTY(qreal cacheBuffer READ cacheBuffer WRITE setCacheBuffer NOTIFY cacheBufferChanged) Q_PROPERTY(
bool useCustomViewport READ useCustomViewport WRITE setUseCustomViewport
NOTIFY useCustomViewportChanged)
Q_PROPERTY(
qreal cacheBuffer READ cacheBuffer WRITE setCacheBuffer NOTIFY
cacheBufferChanged)
// Sizing // Sizing
Q_PROPERTY(qreal estimatedHeight READ estimatedHeight WRITE setEstimatedHeight NOTIFY estimatedHeightChanged) Q_PROPERTY(
qreal estimatedHeight READ estimatedHeight WRITE setEstimatedHeight
NOTIFY estimatedHeightChanged)
// Async // Async
Q_PROPERTY(bool asynchronous READ asynchronous WRITE setAsynchronous NOTIFY asynchronousChanged) Q_PROPERTY(
bool asynchronous READ asynchronous WRITE setAsynchronous NOTIFY
asynchronousChanged)
// Animation Durations // Animation Durations
Q_PROPERTY(int removeDuration READ removeDuration WRITE setRemoveDuration NOTIFY removeDurationChanged) Q_PROPERTY(
Q_PROPERTY(int readyDelay READ readyDelay WRITE setReadyDelay NOTIFY readyDelayChanged) int removeDuration READ removeDuration WRITE setRemoveDuration NOTIFY
removeDurationChanged)
Q_PROPERTY(
int readyDelay READ readyDelay WRITE setReadyDelay NOTIFY
readyDelayChanged)
// State // State
Q_PROPERTY(int count READ count NOTIFY countChanged) Q_PROPERTY(int count READ count NOTIFY countChanged)
public: public:
explicit LazyListView(QQuickItem* parent = nullptr); explicit LazyListView(QQuickItem* parent = nullptr);
~LazyListView() override; ~LazyListView() override;
static LazyListViewAttached* qmlAttachedProperties(QObject* object); static LazyListViewAttached* qmlAttachedProperties(QObject* object);
// Model & Delegate // Model & Delegate
[[nodiscard]] QAbstractItemModel* model() const; [[nodiscard]] QAbstractItemModel* model() const;
void setModel(QAbstractItemModel* model); void setModel(QAbstractItemModel* model);
[[nodiscard]] QQmlComponent* delegate() const; [[nodiscard]] QQmlComponent* delegate() const;
void setDelegate(QQmlComponent* delegate); void setDelegate(QQmlComponent* delegate);
// Layout // Layout
[[nodiscard]] qreal spacing() const; [[nodiscard]] qreal spacing() const;
void setSpacing(qreal spacing); void setSpacing(qreal spacing);
[[nodiscard]] qreal contentHeight() const; [[nodiscard]] qreal contentHeight() const;
[[nodiscard]] qreal layoutHeight() const; [[nodiscard]] qreal layoutHeight() const;
[[nodiscard]] qreal contentY() const; [[nodiscard]] qreal contentY() const;
void setContentY(qreal contentY); void setContentY(qreal contentY);
// Viewport // Viewport
[[nodiscard]] QRectF viewport() const; [[nodiscard]] QRectF viewport() const;
void setViewport(const QRectF& viewport); void setViewport(const QRectF& viewport);
[[nodiscard]] bool useCustomViewport() const; [[nodiscard]] bool useCustomViewport() const;
void setUseCustomViewport(bool use); void setUseCustomViewport(bool use);
[[nodiscard]] qreal cacheBuffer() const; [[nodiscard]] qreal cacheBuffer() const;
void setCacheBuffer(qreal buffer); void setCacheBuffer(qreal buffer);
// Sizing // Sizing
[[nodiscard]] qreal estimatedHeight() const; [[nodiscard]] qreal estimatedHeight() const;
void setEstimatedHeight(qreal height); void setEstimatedHeight(qreal height);
// Async // Async
[[nodiscard]] bool asynchronous() const; [[nodiscard]] bool asynchronous() const;
void setAsynchronous(bool async); void setAsynchronous(bool async);
// Animation Durations // Animation Durations
[[nodiscard]] int removeDuration() const; [[nodiscard]] int removeDuration() const;
void setRemoveDuration(int duration); void setRemoveDuration(int duration);
[[nodiscard]] int readyDelay() const; [[nodiscard]] int readyDelay() const;
void setReadyDelay(int delay); void setReadyDelay(int delay);
// State // State
[[nodiscard]] int count() const; [[nodiscard]] int count() const;
signals: signals:
void modelChanged(); void modelChanged();
void delegateChanged(); void delegateChanged();
void spacingChanged(); void spacingChanged();
void contentHeightChanged(); void contentHeightChanged();
void layoutHeightChanged(); void layoutHeightChanged();
void contentYChanged(); void contentYChanged();
void viewportChanged(); void viewportChanged();
void useCustomViewportChanged(); void useCustomViewportChanged();
void cacheBufferChanged(); void cacheBufferChanged();
void estimatedHeightChanged(); void estimatedHeightChanged();
void asynchronousChanged(); void asynchronousChanged();
void removeDurationChanged(); void removeDurationChanged();
void readyDelayChanged(); void readyDelayChanged();
void countChanged(); void countChanged();
void viewportAdjustNeeded(qreal delta); void viewportAdjustNeeded(qreal delta);
protected: protected:
void componentComplete() override; void componentComplete() override;
void geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) override; void geometryChange(
void updatePolish() override; const QRectF& newGeometry, const QRectF& oldGeometry) override;
void updatePolish() override;
private: private:
struct ItemRecord { struct ItemRecord {
qreal targetY = 0; qreal targetY = 0;
qreal height = 0; qreal height = 0;
bool heightKnown = false; bool heightKnown = false;
bool isNew = false; bool isNew = false;
}; };
struct DelegateEntry { struct DelegateEntry {
int modelIndex = -1; int modelIndex = -1;
QQuickItem* item = nullptr; QQuickItem* item = nullptr;
bool pendingRemoval = false; bool pendingRemoval = false;
bool pendingInsert = false; bool pendingInsert = false;
bool readyDelayStarted = false; bool readyDelayStarted = false;
}; };
// Layout // Layout
void relayout(); void relayout();
[[nodiscard]] std::pair<int, int> computeVisibleRange() const; [[nodiscard]] std::pair<int, int> computeVisibleRange() const;
[[nodiscard]] QRectF effectiveViewport() const; [[nodiscard]] QRectF effectiveViewport() const;
[[nodiscard]] qreal effectiveEstimatedHeight() const; [[nodiscard]] qreal effectiveEstimatedHeight() const;
[[nodiscard]] static qreal delegateHeight(QQuickItem* item); [[nodiscard]] static qreal delegateHeight(QQuickItem* item);
[[nodiscard]] static qreal delegateVisibleHeight(QQuickItem* item); [[nodiscard]] static qreal delegateVisibleHeight(QQuickItem* item);
[[nodiscard]] static bool isDelegateReady(QQuickItem* item); [[nodiscard]] static bool isDelegateReady(QQuickItem* item);
void trackHeight(qreal height); void trackHeight(qreal height);
void untrackHeight(qreal height); void untrackHeight(qreal height);
// Delegate lifecycle // Delegate lifecycle
void syncDelegates(); void syncDelegates();
DelegateEntry createDelegate(int modelIndex); DelegateEntry createDelegate(int modelIndex);
void destroyDelegate(DelegateEntry& entry); void destroyDelegate(DelegateEntry& entry);
void updateDelegateData(DelegateEntry& entry); void updateDelegateData(DelegateEntry& entry);
// Model connection // Model connection
void connectModel(); void connectModel();
void disconnectModel(); void disconnectModel();
void resetContent(); void resetContent();
void onRowsInserted(const QModelIndex& parent, int first, int last); void onRowsInserted(const QModelIndex& parent, int first, int last);
void onRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last); void onRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last);
void onRowsRemoved(const QModelIndex& parent, int first, int last); void onRowsRemoved(const QModelIndex& parent, int first, int last);
void onRowsMoved(const QModelIndex& parent, int start, int end, const QModelIndex& destination, int row); void onRowsMoved(
void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QList<int>& roles); const QModelIndex& parent,
void onModelReset(); int start,
int end,
const QModelIndex& destination,
int row);
void onDataChanged(
const QModelIndex& topLeft,
const QModelIndex& bottomRight,
const QList<int>& roles);
void onModelReset();
// Members // Members
QAbstractItemModel* m_model = nullptr; QAbstractItemModel* m_model = nullptr;
QQmlComponent* m_delegate = nullptr; QQmlComponent* m_delegate = nullptr;
qreal m_spacing = 0; qreal m_spacing = 0;
qreal m_contentHeight = 0; qreal m_contentHeight = 0;
qreal m_layoutHeight = 0; qreal m_layoutHeight = 0;
qreal m_contentY = 0; qreal m_contentY = 0;
QRectF m_viewport; QRectF m_viewport;
bool m_useCustomViewport = false; bool m_useCustomViewport = false;
qreal m_cacheBuffer = 0; qreal m_cacheBuffer = 0;
qreal m_estimatedHeight = -1; qreal m_estimatedHeight = -1;
qreal m_knownHeightSum = 0; qreal m_knownHeightSum = 0;
int m_knownHeightCount = 0; int m_knownHeightCount = 0;
bool m_asynchronous = false; bool m_asynchronous = false;
int m_removeDuration = 300; int m_removeDuration = 300;
int m_readyDelay = 0; int m_readyDelay = 0;
QVector<ItemRecord> m_layout; QVector<ItemRecord> m_layout;
QHash<int, DelegateEntry> m_delegates; QHash<int, DelegateEntry> m_delegates;
QHash<QQuickItem*, int> m_itemToIndex; QHash<QQuickItem*, int> m_itemToIndex;
QVector<DelegateEntry> m_dyingDelegates; QVector<DelegateEntry> m_dyingDelegates;
bool m_componentComplete = false; bool m_componentComplete = false;
bool m_relayoutPending = false; bool m_relayoutPending = false;
QList<QMetaObject::Connection> m_modelConnections; QList<QMetaObject::Connection> m_modelConnections;
}; };
} // namespace ZShell::components } // namespace ZShell::components
+6 -2
View File
@@ -194,7 +194,8 @@ void WavyLine::paintLinear(QPainter* painter) {
bool first = true; bool first = true;
for (int x = m_lineWidth / 2; x <= drawEnd; ++x) { for (int x = m_lineWidth / 2; x <= drawEnd; ++x) {
const auto theta = m_frequency * 2 * M_PI * (x + m_startX) / len + phase; const auto theta =
m_frequency * 2 * M_PI * (x + m_startX) / len + phase;
const auto waveY = centerY + amplitude * qSin(theta); const auto waveY = centerY + amplitude * qSin(theta);
if (first) { if (first) {
path.moveTo(x, waveY); path.moveTo(x, waveY);
@@ -215,7 +216,10 @@ void WavyLine::paintArc(QPainter* painter) {
const auto amplitude = m_lineWidth * m_amplitudeMultiplier; const auto amplitude = m_lineWidth * m_amplitudeMultiplier;
const auto cx = width() / 2.0; const auto cx = width() / 2.0;
const auto cy = height() / 2.0; const auto cy = height() / 2.0;
const auto radius = m_radius > 0 ? m_radius : (qMin(width(), height()) - m_lineWidth - 2 * amplitude) / 2.0; const auto radius =
m_radius > 0
? m_radius
: (qMin(width(), height()) - m_lineWidth - 2 * amplitude) / 2.0;
if (radius <= 0) { if (radius <= 0) {
return; return;
+91 -77
View File
@@ -6,102 +6,116 @@
namespace ZShell::controls { namespace ZShell::controls {
class WavyLine : public QQuickPaintedItem { class WavyLine : public QQuickPaintedItem {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(int lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged FINAL) Q_PROPERTY(
Q_PROPERTY(qreal amplitudeMultiplier READ amplitudeMultiplier WRITE setAmplitudeMultiplier NOTIFY int lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged
amplitudeMultiplierChanged FINAL) FINAL)
Q_PROPERTY(int frequency READ frequency WRITE setFrequency NOTIFY frequencyChanged FINAL) Q_PROPERTY(
Q_PROPERTY(qreal startX READ startX WRITE setStartX NOTIFY startXChanged FINAL) qreal amplitudeMultiplier READ amplitudeMultiplier WRITE
Q_PROPERTY(qreal fullLength READ fullLength WRITE setFullLength NOTIFY fullLengthChanged FINAL) setAmplitudeMultiplier NOTIFY amplitudeMultiplierChanged FINAL)
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL) Q_PROPERTY(
Q_PROPERTY(qreal waveProgress READ waveProgress WRITE setWaveProgress NOTIFY waveProgressChanged FINAL) int frequency READ frequency WRITE setFrequency NOTIFY frequencyChanged
Q_PROPERTY(PathType pathType READ pathType WRITE setPathType NOTIFY pathTypeChanged FINAL) FINAL)
Q_PROPERTY(qreal startAngle READ startAngle WRITE setStartAngle NOTIFY startAngleChanged FINAL) Q_PROPERTY(
Q_PROPERTY(qreal fullAngle READ fullAngle WRITE setFullAngle NOTIFY fullAngleChanged FINAL) qreal startX READ startX WRITE setStartX NOTIFY startXChanged FINAL)
Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged FINAL) Q_PROPERTY(
Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL) qreal fullLength READ fullLength WRITE setFullLength NOTIFY
fullLengthChanged FINAL)
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL)
Q_PROPERTY(
qreal waveProgress READ waveProgress WRITE setWaveProgress NOTIFY
waveProgressChanged FINAL)
Q_PROPERTY(
PathType pathType READ pathType WRITE setPathType NOTIFY pathTypeChanged
FINAL)
Q_PROPERTY(
qreal startAngle READ startAngle WRITE setStartAngle NOTIFY
startAngleChanged FINAL)
Q_PROPERTY(
qreal fullAngle READ fullAngle WRITE setFullAngle NOTIFY
fullAngleChanged FINAL)
Q_PROPERTY(
qreal radius READ radius WRITE setRadius NOTIFY radiusChanged FINAL)
Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL)
public: public:
enum PathType { enum PathType { Linear, Arc };
Linear, Q_ENUM(PathType)
Arc
};
Q_ENUM(PathType)
explicit WavyLine(QQuickItem* parent = nullptr); explicit WavyLine(QQuickItem* parent = nullptr);
[[nodiscard]] int lineWidth() const; [[nodiscard]] int lineWidth() const;
void setLineWidth(int lineWidth); void setLineWidth(int lineWidth);
[[nodiscard]] qreal amplitudeMultiplier() const; [[nodiscard]] qreal amplitudeMultiplier() const;
void setAmplitudeMultiplier(qreal amplitudeMultiplier); void setAmplitudeMultiplier(qreal amplitudeMultiplier);
[[nodiscard]] int frequency() const; [[nodiscard]] int frequency() const;
void setFrequency(int frequency); void setFrequency(int frequency);
[[nodiscard]] qreal startX() const; [[nodiscard]] qreal startX() const;
void setStartX(qreal startX); void setStartX(qreal startX);
[[nodiscard]] qreal fullLength() const; [[nodiscard]] qreal fullLength() const;
void setFullLength(qreal fullLength); void setFullLength(qreal fullLength);
[[nodiscard]] QColor color() const; [[nodiscard]] QColor color() const;
void setColor(const QColor& color); void setColor(const QColor& color);
[[nodiscard]] qreal waveProgress() const; [[nodiscard]] qreal waveProgress() const;
void setWaveProgress(qreal progress); void setWaveProgress(qreal progress);
[[nodiscard]] PathType pathType() const; [[nodiscard]] PathType pathType() const;
void setPathType(PathType pathType); void setPathType(PathType pathType);
[[nodiscard]] qreal startAngle() const; [[nodiscard]] qreal startAngle() const;
void setStartAngle(qreal startAngle); void setStartAngle(qreal startAngle);
[[nodiscard]] qreal fullAngle() const; [[nodiscard]] qreal fullAngle() const;
void setFullAngle(qreal fullAngle); void setFullAngle(qreal fullAngle);
[[nodiscard]] qreal radius() const; [[nodiscard]] qreal radius() const;
void setRadius(qreal radius); void setRadius(qreal radius);
[[nodiscard]] qreal value() const; [[nodiscard]] qreal value() const;
void setValue(qreal value); void setValue(qreal value);
void paint(QPainter* painter) override; void paint(QPainter* painter) override;
signals: signals:
void lineWidthChanged(); void lineWidthChanged();
void amplitudeMultiplierChanged(); void amplitudeMultiplierChanged();
void frequencyChanged(); void frequencyChanged();
void startXChanged(); void startXChanged();
void fullLengthChanged(); void fullLengthChanged();
void colorChanged(); void colorChanged();
void waveProgressChanged(); void waveProgressChanged();
void pathTypeChanged(); void pathTypeChanged();
void startAngleChanged(); void startAngleChanged();
void fullAngleChanged(); void fullAngleChanged();
void radiusChanged(); void radiusChanged();
void valueChanged(); void valueChanged();
private: private:
void paintLinear(QPainter* painter); void paintLinear(QPainter* painter);
void paintArc(QPainter* painter); void paintArc(QPainter* painter);
int m_lineWidth; int m_lineWidth;
qreal m_amplitudeMultiplier; qreal m_amplitudeMultiplier;
int m_frequency; int m_frequency;
qreal m_startX; qreal m_startX;
qreal m_fullLength; qreal m_fullLength;
QColor m_color; QColor m_color;
qreal m_waveProgress; qreal m_waveProgress;
PathType m_pathType; PathType m_pathType;
qreal m_startAngle; qreal m_startAngle;
qreal m_fullAngle; qreal m_fullAngle;
qreal m_radius; qreal m_radius;
qreal m_value; qreal m_value;
qreal m_startAngleRad; qreal m_startAngleRad;
qreal m_fullAngleRad; qreal m_fullAngleRad;
}; };
} // namespace ZShell::controls } // namespace ZShell::controls
+9 -15
View File
@@ -6,8 +6,7 @@
namespace ZShell::internal { namespace ZShell::internal {
ArcGauge::ArcGauge(QQuickItem* parent) ArcGauge::ArcGauge(QQuickItem* parent) : QQuickPaintedItem(parent) {
: QQuickPaintedItem(parent) {
setAntialiasing(true); setAntialiasing(true);
} }
@@ -36,7 +35,8 @@ void ArcGauge::paint(QPainter* painter) {
// Draw value arc // Draw value arc
if (m_percentage > 0.0) { if (m_percentage > 0.0) {
const int valueSweep16 = qRound(static_cast<qreal>(sweepAngle16) * m_percentage); const int valueSweep16 =
qRound(static_cast<qreal>(sweepAngle16) * m_percentage);
QPen valuePen(m_accentColor, m_lineWidth); QPen valuePen(m_accentColor, m_lineWidth);
valuePen.setCapStyle(Qt::RoundCap); valuePen.setCapStyle(Qt::RoundCap);
painter->setPen(valuePen); painter->setPen(valuePen);
@@ -49,8 +49,7 @@ qreal ArcGauge::percentage() const {
} }
void ArcGauge::setPercentage(qreal percentage) { void ArcGauge::setPercentage(qreal percentage) {
if (qFuzzyCompare(m_percentage, percentage)) if (qFuzzyCompare(m_percentage, percentage)) return;
return;
m_percentage = percentage; m_percentage = percentage;
emit percentageChanged(); emit percentageChanged();
update(); update();
@@ -61,8 +60,7 @@ QColor ArcGauge::accentColor() const {
} }
void ArcGauge::setAccentColor(const QColor& color) { void ArcGauge::setAccentColor(const QColor& color) {
if (m_accentColor == color) if (m_accentColor == color) return;
return;
m_accentColor = color; m_accentColor = color;
emit accentColorChanged(); emit accentColorChanged();
update(); update();
@@ -73,8 +71,7 @@ QColor ArcGauge::trackColor() const {
} }
void ArcGauge::setTrackColor(const QColor& color) { void ArcGauge::setTrackColor(const QColor& color) {
if (m_trackColor == color) if (m_trackColor == color) return;
return;
m_trackColor = color; m_trackColor = color;
emit trackColorChanged(); emit trackColorChanged();
update(); update();
@@ -85,8 +82,7 @@ qreal ArcGauge::startAngle() const {
} }
void ArcGauge::setStartAngle(qreal angle) { void ArcGauge::setStartAngle(qreal angle) {
if (qFuzzyCompare(m_startAngle, angle)) if (qFuzzyCompare(m_startAngle, angle)) return;
return;
m_startAngle = angle; m_startAngle = angle;
emit startAngleChanged(); emit startAngleChanged();
update(); update();
@@ -97,8 +93,7 @@ qreal ArcGauge::sweepAngle() const {
} }
void ArcGauge::setSweepAngle(qreal angle) { void ArcGauge::setSweepAngle(qreal angle) {
if (qFuzzyCompare(m_sweepAngle, angle)) if (qFuzzyCompare(m_sweepAngle, angle)) return;
return;
m_sweepAngle = angle; m_sweepAngle = angle;
emit sweepAngleChanged(); emit sweepAngleChanged();
update(); update();
@@ -109,8 +104,7 @@ qreal ArcGauge::lineWidth() const {
} }
void ArcGauge::setLineWidth(qreal width) { void ArcGauge::setLineWidth(qreal width) {
if (qFuzzyCompare(m_lineWidth, width)) if (qFuzzyCompare(m_lineWidth, width)) return;
return;
m_lineWidth = width; m_lineWidth = width;
emit lineWidthChanged(); emit lineWidthChanged();
update(); update();
+50 -38
View File
@@ -8,54 +8,66 @@
namespace ZShell::internal { namespace ZShell::internal {
class ArcGauge : public QQuickPaintedItem { class ArcGauge : public QQuickPaintedItem {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(qreal percentage READ percentage WRITE setPercentage NOTIFY percentageChanged) Q_PROPERTY(
Q_PROPERTY(QColor accentColor READ accentColor WRITE setAccentColor NOTIFY accentColorChanged) qreal percentage READ percentage WRITE setPercentage NOTIFY
Q_PROPERTY(QColor trackColor READ trackColor WRITE setTrackColor NOTIFY trackColorChanged) percentageChanged)
Q_PROPERTY(qreal startAngle READ startAngle WRITE setStartAngle NOTIFY startAngleChanged) Q_PROPERTY(
Q_PROPERTY(qreal sweepAngle READ sweepAngle WRITE setSweepAngle NOTIFY sweepAngleChanged) QColor accentColor READ accentColor WRITE setAccentColor NOTIFY
Q_PROPERTY(qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged) accentColorChanged)
Q_PROPERTY(
QColor trackColor READ trackColor WRITE setTrackColor NOTIFY
trackColorChanged)
Q_PROPERTY(
qreal startAngle READ startAngle WRITE setStartAngle NOTIFY
startAngleChanged)
Q_PROPERTY(
qreal sweepAngle READ sweepAngle WRITE setSweepAngle NOTIFY
sweepAngleChanged)
Q_PROPERTY(
qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY
lineWidthChanged)
public: public:
explicit ArcGauge(QQuickItem* parent = nullptr); explicit ArcGauge(QQuickItem* parent = nullptr);
void paint(QPainter* painter) override; void paint(QPainter* painter) override;
[[nodiscard]] qreal percentage() const; [[nodiscard]] qreal percentage() const;
void setPercentage(qreal percentage); void setPercentage(qreal percentage);
[[nodiscard]] QColor accentColor() const; [[nodiscard]] QColor accentColor() const;
void setAccentColor(const QColor& color); void setAccentColor(const QColor& color);
[[nodiscard]] QColor trackColor() const; [[nodiscard]] QColor trackColor() const;
void setTrackColor(const QColor& color); void setTrackColor(const QColor& color);
[[nodiscard]] qreal startAngle() const; [[nodiscard]] qreal startAngle() const;
void setStartAngle(qreal angle); void setStartAngle(qreal angle);
[[nodiscard]] qreal sweepAngle() const; [[nodiscard]] qreal sweepAngle() const;
void setSweepAngle(qreal angle); void setSweepAngle(qreal angle);
[[nodiscard]] qreal lineWidth() const; [[nodiscard]] qreal lineWidth() const;
void setLineWidth(qreal width); void setLineWidth(qreal width);
signals: signals:
void percentageChanged(); void percentageChanged();
void accentColorChanged(); void accentColorChanged();
void trackColorChanged(); void trackColorChanged();
void startAngleChanged(); void startAngleChanged();
void sweepAngleChanged(); void sweepAngleChanged();
void lineWidthChanged(); void lineWidthChanged();
private: private:
qreal m_percentage = 0.0; qreal m_percentage = 0.0;
QColor m_accentColor; QColor m_accentColor;
QColor m_trackColor; QColor m_trackColor;
qreal m_startAngle = 0.75 * M_PI; qreal m_startAngle = 0.75 * M_PI;
qreal m_sweepAngle = 1.5 * M_PI; qreal m_sweepAngle = 1.5 * M_PI;
qreal m_lineWidth = 10.0; qreal m_lineWidth = 10.0;
}; };
} // namespace ZShell::Internal } // namespace ZShell::internal
+157 -136
View File
@@ -12,212 +12,233 @@
namespace ZShell::internal { namespace ZShell::internal {
qreal CachingImageManager::effectiveScale() const { qreal CachingImageManager::effectiveScale() const {
if (m_item && m_item->window()) { if (m_item && m_item->window()) {
return m_item->window()->devicePixelRatio(); return m_item->window()->devicePixelRatio();
} }
return 1.0; return 1.0;
} }
QSize CachingImageManager::effectiveSize() const { QSize CachingImageManager::effectiveSize() const {
if (!m_item) { if (!m_item) {
return QSize(); return QSize();
} }
const qreal scale = effectiveScale(); const qreal scale = effectiveScale();
const QSize size = QSizeF(m_item->width() * scale, m_item->height() * scale).toSize(); const QSize size =
m_item->setProperty("sourceSize", size); QSizeF(m_item->width() * scale, m_item->height() * scale).toSize();
return size; m_item->setProperty("sourceSize", size);
return size;
} }
QQuickItem* CachingImageManager::item() const { QQuickItem* CachingImageManager::item() const {
return m_item; return m_item;
} }
void CachingImageManager::setItem(QQuickItem* item) { void CachingImageManager::setItem(QQuickItem* item) {
if (m_item == item) { if (m_item == item) {
return; return;
} }
if (m_widthConn) { if (m_widthConn) {
disconnect(m_widthConn); disconnect(m_widthConn);
} }
if (m_heightConn) { if (m_heightConn) {
disconnect(m_heightConn); disconnect(m_heightConn);
} }
m_item = item; m_item = item;
emit itemChanged(); emit itemChanged();
if (item) { if (item) {
m_widthConn = connect(item, &QQuickItem::widthChanged, this, [this]() { m_widthConn = connect(item, &QQuickItem::widthChanged, this, [this]() {
updateSource(); updateSource();
}); });
m_heightConn = connect(item, &QQuickItem::heightChanged, this, [this]() { m_heightConn =
updateSource(); connect(item, &QQuickItem::heightChanged, this, [this]() {
}); updateSource();
updateSource(); });
} updateSource();
}
} }
QUrl CachingImageManager::cacheDir() const { QUrl CachingImageManager::cacheDir() const {
return m_cacheDir; return m_cacheDir;
} }
void CachingImageManager::setCacheDir(const QUrl& cacheDir) { void CachingImageManager::setCacheDir(const QUrl& cacheDir) {
if (m_cacheDir == cacheDir) { if (m_cacheDir == cacheDir) {
return; return;
} }
m_cacheDir = cacheDir; m_cacheDir = cacheDir;
if (!m_cacheDir.path().endsWith("/")) { if (!m_cacheDir.path().endsWith("/")) {
m_cacheDir.setPath(m_cacheDir.path() + "/"); m_cacheDir.setPath(m_cacheDir.path() + "/");
} }
emit cacheDirChanged(); emit cacheDirChanged();
} }
QString CachingImageManager::path() const { QString CachingImageManager::path() const {
return m_path; return m_path;
} }
void CachingImageManager::setPath(const QString& path) { void CachingImageManager::setPath(const QString& path) {
if (m_path == path) { if (m_path == path) {
return; return;
} }
m_path = path; m_path = path;
emit pathChanged(); emit pathChanged();
if (!path.isEmpty()) { if (!path.isEmpty()) {
updateSource(path); updateSource(path);
} }
} }
void CachingImageManager::updateSource() { void CachingImageManager::updateSource() {
updateSource(m_path); updateSource(m_path);
} }
void CachingImageManager::updateSource(const QString& path) { void CachingImageManager::updateSource(const QString& path) {
if (path.isEmpty() || path == m_shaPath) { if (path.isEmpty() || path == m_shaPath) {
// Path is empty or already calculating sha for path // Path is empty or already calculating sha for path
return; return;
} }
m_shaPath = path; m_shaPath = path;
const auto future = QtConcurrent::run(&CachingImageManager::sha256sum, path); const auto future =
QtConcurrent::run(&CachingImageManager::sha256sum, path);
const auto watcher = new QFutureWatcher<QString>(this); const auto watcher = new QFutureWatcher<QString>(this);
connect(watcher, &QFutureWatcher<QString>::finished, this, [watcher, path, this]() { connect(
if (m_path != path) { watcher,
// Object is destroyed or path has changed, ignore &QFutureWatcher<QString>::finished,
watcher->deleteLater(); this,
return; [watcher, path, this]() {
} if (m_path != path) {
// Object is destroyed or path has changed, ignore
watcher->deleteLater();
return;
}
const QSize size = effectiveSize(); const QSize size = effectiveSize();
if (!m_item || !size.width() || !size.height()) { if (!m_item || !size.width() || !size.height()) {
watcher->deleteLater(); watcher->deleteLater();
return; return;
} }
const QString fillMode = m_item->property("fillMode").toString(); const QString fillMode = m_item->property("fillMode").toString();
// clang-format off // clang-format off
const QString filename = QString("%1@%2x%3-%4.png") const QString filename = QString("%1@%2x%3-%4.png")
.arg(watcher->result()).arg(size.width()).arg(size.height()) .arg(watcher->result()).arg(size.width()).arg(size.height())
.arg(fillMode == "PreserveAspectCrop" ? "crop" : fillMode == "PreserveAspectFit" ? "fit" : "stretch"); .arg(fillMode == "PreserveAspectCrop" ? "crop" : fillMode == "PreserveAspectFit" ? "fit" : "stretch");
// clang-format on // clang-format on
const QUrl cache = m_cacheDir.resolved(QUrl(filename)); const QUrl cache = m_cacheDir.resolved(QUrl(filename));
if (m_cachePath == cache) { if (m_cachePath == cache) {
watcher->deleteLater(); watcher->deleteLater();
return; return;
} }
m_cachePath = cache; m_cachePath = cache;
emit cachePathChanged(); emit cachePathChanged();
if (!cache.isLocalFile()) { if (!cache.isLocalFile()) {
qWarning() << "CachingImageManager::updateSource: cachePath" << cache << "is not a local file"; qWarning() << "CachingImageManager::updateSource: cachePath"
watcher->deleteLater(); << cache << "is not a local file";
return; watcher->deleteLater();
} return;
}
const QImageReader reader(cache.toLocalFile()); const QImageReader reader(cache.toLocalFile());
if (reader.canRead()) { if (reader.canRead()) {
m_item->setProperty("source", cache); m_item->setProperty("source", cache);
} else { } else {
m_item->setProperty("source", QUrl::fromLocalFile(path)); m_item->setProperty("source", QUrl::fromLocalFile(path));
createCache(path, cache.toLocalFile(), fillMode, size); createCache(path, cache.toLocalFile(), fillMode, size);
} }
// Clear current running sha if same // Clear current running sha if same
if (m_shaPath == path) { if (m_shaPath == path) {
m_shaPath = QString(); m_shaPath = QString();
} }
watcher->deleteLater(); watcher->deleteLater();
}); });
watcher->setFuture(future); watcher->setFuture(future);
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
} }
QUrl CachingImageManager::cachePath() const { QUrl CachingImageManager::cachePath() const {
return m_cachePath; return m_cachePath;
} }
void CachingImageManager::createCache( void CachingImageManager::createCache(
const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const { const QString& path,
QThreadPool::globalInstance()->start([path, cache, fillMode, size] { const QString& cache,
QImage image(path); const QString& fillMode,
const QSize& size) const {
QThreadPool::globalInstance()->start([path, cache, fillMode, size] {
QImage image(path);
if (image.isNull()) { if (image.isNull()) {
qWarning() << "CachingImageManager::createCache: failed to read" << path; qWarning() << "CachingImageManager::createCache: failed to read"
return; << path;
} return;
}
image.convertTo(QImage::Format_ARGB32); image.convertTo(QImage::Format_ARGB32);
if (fillMode == "PreserveAspectCrop") { if (fillMode == "PreserveAspectCrop") {
image = image.scaled(size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); image = image.scaled(
} else if (fillMode == "PreserveAspectFit") { size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
image = image.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); } else if (fillMode == "PreserveAspectFit") {
} else { image = image.scaled(
image = image.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
} } else {
image = image.scaled(
size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
if (fillMode == "PreserveAspectCrop" || fillMode == "PreserveAspectFit") { if (fillMode == "PreserveAspectCrop" ||
QImage canvas(size, QImage::Format_ARGB32); fillMode == "PreserveAspectFit") {
canvas.fill(Qt::transparent); QImage canvas(size, QImage::Format_ARGB32);
canvas.fill(Qt::transparent);
QPainter painter(&canvas); QPainter painter(&canvas);
painter.drawImage((size.width() - image.width()) / 2, (size.height() - image.height()) / 2, image); painter.drawImage(
painter.end(); (size.width() - image.width()) / 2,
(size.height() - image.height()) / 2,
image);
painter.end();
image = canvas; image = canvas;
} }
const QString parent = QFileInfo(cache).absolutePath(); const QString parent = QFileInfo(cache).absolutePath();
if (!QDir().mkpath(parent) || !image.save(cache)) { if (!QDir().mkpath(parent) || !image.save(cache)) {
qWarning() << "CachingImageManager::createCache: failed to save to" << cache; qWarning() << "CachingImageManager::createCache: failed to save to"
} << cache;
}); }
});
} }
QString CachingImageManager::sha256sum(const QString& path) { QString CachingImageManager::sha256sum(const QString& path) {
QFile file(path); QFile file(path);
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
qWarning() << "CachingImageManager::sha256sum: failed to open" << path; qWarning() << "CachingImageManager::sha256sum: failed to open" << path;
return ""; return "";
} }
QCryptographicHash hash(QCryptographicHash::Sha256); QCryptographicHash hash(QCryptographicHash::Sha256);
hash.addData(&file); hash.addData(&file);
file.close(); file.close();
return hash.result().toHex(); return hash.result().toHex();
} }
} // namespace ZShell::internal } // namespace ZShell::internal
+43 -38
View File
@@ -7,60 +7,65 @@
namespace ZShell::internal { namespace ZShell::internal {
class CachingImageManager : public QObject { class CachingImageManager : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(QQuickItem* item READ item WRITE setItem NOTIFY itemChanged REQUIRED) Q_PROPERTY(
Q_PROPERTY(QUrl cacheDir READ cacheDir WRITE setCacheDir NOTIFY cacheDirChanged REQUIRED) QQuickItem* item READ item WRITE setItem NOTIFY itemChanged REQUIRED)
Q_PROPERTY(
QUrl cacheDir READ cacheDir WRITE setCacheDir NOTIFY cacheDirChanged
REQUIRED)
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(QUrl cachePath READ cachePath NOTIFY cachePathChanged) Q_PROPERTY(QUrl cachePath READ cachePath NOTIFY cachePathChanged)
public: public:
explicit CachingImageManager(QObject* parent = nullptr) explicit CachingImageManager(QObject* parent = nullptr)
: QObject(parent) : QObject(parent), m_item(nullptr) {}
, m_item(nullptr) {
}
[[nodiscard]] QQuickItem* item() const; [[nodiscard]] QQuickItem* item() const;
void setItem(QQuickItem* item); void setItem(QQuickItem* item);
[[nodiscard]] QUrl cacheDir() const; [[nodiscard]] QUrl cacheDir() const;
void setCacheDir(const QUrl& cacheDir); void setCacheDir(const QUrl& cacheDir);
[[nodiscard]] QString path() const; [[nodiscard]] QString path() const;
void setPath(const QString& path); void setPath(const QString& path);
[[nodiscard]] QUrl cachePath() const; [[nodiscard]] QUrl cachePath() const;
Q_INVOKABLE void updateSource(); Q_INVOKABLE void updateSource();
Q_INVOKABLE void updateSource(const QString& path); Q_INVOKABLE void updateSource(const QString& path);
signals: signals:
void itemChanged(); void itemChanged();
void cacheDirChanged(); void cacheDirChanged();
void pathChanged(); void pathChanged();
void cachePathChanged(); void cachePathChanged();
void usingCacheChanged(); void usingCacheChanged();
private: private:
QString m_shaPath; QString m_shaPath;
QQuickItem* m_item; QQuickItem* m_item;
QUrl m_cacheDir; QUrl m_cacheDir;
QString m_path; QString m_path;
QUrl m_cachePath; QUrl m_cachePath;
QMetaObject::Connection m_widthConn; QMetaObject::Connection m_widthConn;
QMetaObject::Connection m_heightConn; QMetaObject::Connection m_heightConn;
[[nodiscard]] qreal effectiveScale() const; [[nodiscard]] qreal effectiveScale() const;
[[nodiscard]] QSize effectiveSize() const; [[nodiscard]] QSize effectiveSize() const;
void createCache(const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const; void createCache(
[[nodiscard]] static QString sha256sum(const QString& path); const QString& path,
const QString& cache,
const QString& fillMode,
const QSize& size) const;
[[nodiscard]] static QString sha256sum(const QString& path);
}; };
} // namespace ZShell::internal } // namespace ZShell::internal
+9 -16
View File
@@ -4,19 +4,15 @@
namespace ZShell::internal { namespace ZShell::internal {
CircularBuffer::CircularBuffer(QObject* parent) CircularBuffer::CircularBuffer(QObject* parent) : QObject(parent) {}
: QObject(parent) {
}
int CircularBuffer::capacity() const { int CircularBuffer::capacity() const {
return m_capacity; return m_capacity;
} }
void CircularBuffer::setCapacity(int capacity) { void CircularBuffer::setCapacity(int capacity) {
if (capacity < 0) if (capacity < 0) capacity = 0;
capacity = 0; if (m_capacity == capacity) return;
if (m_capacity == capacity)
return;
const auto old = values(); const auto old = values();
@@ -52,8 +48,7 @@ QList<qreal> CircularBuffer::values() const {
} }
qreal CircularBuffer::maximum() const { qreal CircularBuffer::maximum() const {
if (m_count == 0) if (m_count == 0) return 0.0;
return 0.0;
qreal maxVal = at(0); qreal maxVal = at(0);
for (int i = 1; i < m_count; ++i) for (int i = 1; i < m_count; ++i)
@@ -62,8 +57,7 @@ qreal CircularBuffer::maximum() const {
} }
void CircularBuffer::push(qreal value) { void CircularBuffer::push(qreal value) {
if (m_capacity <= 0) if (m_capacity <= 0) return;
return;
m_data[m_head] = value; m_data[m_head] = value;
m_head = (m_head + 1) % m_capacity; m_head = (m_head + 1) % m_capacity;
@@ -75,8 +69,7 @@ void CircularBuffer::push(qreal value) {
} }
void CircularBuffer::clear() { void CircularBuffer::clear() {
if (m_count == 0) if (m_count == 0) return;
return;
m_head = 0; m_head = 0;
m_count = 0; m_count = 0;
@@ -85,10 +78,10 @@ void CircularBuffer::clear() {
} }
qreal CircularBuffer::at(int index) const { qreal CircularBuffer::at(int index) const {
if (index < 0 || index >= m_count) if (index < 0 || index >= m_count) return 0.0;
return 0.0;
const int actualIndex = (m_head - m_count + index + m_capacity) % m_capacity; const int actualIndex =
(m_head - m_count + index + m_capacity) % m_capacity;
return m_data[actualIndex]; return m_data[actualIndex];
} }
+26 -25
View File
@@ -7,38 +7,39 @@
namespace ZShell::internal { namespace ZShell::internal {
class CircularBuffer : public QObject { class CircularBuffer : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(int capacity READ capacity WRITE setCapacity NOTIFY capacityChanged) Q_PROPERTY(
Q_PROPERTY(int count READ count NOTIFY countChanged) int capacity READ capacity WRITE setCapacity NOTIFY capacityChanged)
Q_PROPERTY(QList<qreal> values READ values NOTIFY valuesChanged) Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(qreal maximum READ maximum NOTIFY valuesChanged) Q_PROPERTY(QList<qreal> values READ values NOTIFY valuesChanged)
Q_PROPERTY(qreal maximum READ maximum NOTIFY valuesChanged)
public: public:
explicit CircularBuffer(QObject* parent = nullptr); explicit CircularBuffer(QObject* parent = nullptr);
[[nodiscard]] int capacity() const; [[nodiscard]] int capacity() const;
void setCapacity(int capacity); void setCapacity(int capacity);
[[nodiscard]] int count() const; [[nodiscard]] int count() const;
[[nodiscard]] QList<qreal> values() const; [[nodiscard]] QList<qreal> values() const;
[[nodiscard]] qreal maximum() const; [[nodiscard]] qreal maximum() const;
Q_INVOKABLE void push(qreal value); Q_INVOKABLE void push(qreal value);
Q_INVOKABLE void clear(); Q_INVOKABLE void clear();
Q_INVOKABLE [[nodiscard]] qreal at(int index) const; Q_INVOKABLE [[nodiscard]] qreal at(int index) const;
signals: signals:
void capacityChanged(); void capacityChanged();
void countChanged(); void countChanged();
void valuesChanged(); void valuesChanged();
private: private:
QVector<qreal> m_data; QVector<qreal> m_data;
int m_head = 0; int m_head = 0;
int m_count = 0; int m_count = 0;
int m_capacity = 0; int m_capacity = 0;
}; };
} // namespace ZShell::internal } // namespace ZShell::internal
@@ -15,8 +15,10 @@ constexpr qint32 TAIL_DEGREES_OFFSET = -20;
constexpr qint32 EXTRA_DEGREES_PER_CYCLE = 250; constexpr qint32 EXTRA_DEGREES_PER_CYCLE = 250;
constexpr qint32 CONSTANT_ROTATION_DEGREES = 1520; constexpr qint32 CONSTANT_ROTATION_DEGREES = 1520;
constexpr std::array<qint32, TOTAL_CYCLES> DELAY_TO_EXPAND_IN_MS = { 0, 1350, 2700, 4050 }; constexpr std::array<qint32, TOTAL_CYCLES> DELAY_TO_EXPAND_IN_MS = {
constexpr std::array<qint32, TOTAL_CYCLES> DELAY_TO_COLLAPSE_IN_MS = { 667, 2017, 3367, 4717 }; 0, 1350, 2700, 4050};
constexpr std::array<qint32, TOTAL_CYCLES> DELAY_TO_COLLAPSE_IN_MS = {
667, 2017, 3367, 4717};
} // namespace advance } // namespace advance
@@ -26,7 +28,7 @@ constexpr qint32 TOTAL_DURATION_IN_MS = 6000;
constexpr qint32 DURATION_SPIN_IN_MS = 500; constexpr qint32 DURATION_SPIN_IN_MS = 500;
constexpr qint32 DURATION_GROW_ACTIVE_IN_MS = 3000; constexpr qint32 DURATION_GROW_ACTIVE_IN_MS = 3000;
constexpr qint32 DURATION_SHRINK_ACTIVE_IN_MS = 3000; constexpr qint32 DURATION_SHRINK_ACTIVE_IN_MS = 3000;
constexpr std::array DELAY_SPINS_IN_MS = { 0, 1500, 3000, 4500 }; constexpr std::array DELAY_SPINS_IN_MS = {0, 1500, 3000, 4500};
constexpr qint32 DELAY_GROW_ACTIVE_IN_MS = 0; constexpr qint32 DELAY_GROW_ACTIVE_IN_MS = 0;
constexpr qint32 DELAY_SHRINK_ACTIVE_IN_MS = 3000; constexpr qint32 DELAY_SHRINK_ACTIVE_IN_MS = 3000;
constexpr qint32 DURATION_TO_COMPLETE_END_IN_MS = 500; constexpr qint32 DURATION_TO_COMPLETE_END_IN_MS = 500;
@@ -38,7 +40,7 @@ constexpr qint32 CONSTANT_ROTATION_DEGREES = 1080;
// Despite of the constant rotation, there are also 5 extra rotations the entire animation. The // Despite of the constant rotation, there are also 5 extra rotations the entire animation. The
// total degrees that each extra rotation goes by. // total degrees that each extra rotation goes by.
constexpr qint32 SPIN_ROTATION_DEGREES = 90; constexpr qint32 SPIN_ROTATION_DEGREES = 90;
constexpr std::array<qreal, 2> END_FRACTION_RANGE = { 0.10, 0.87 }; constexpr std::array<qreal, 2> END_FRACTION_RANGE = {0.10, 0.87};
} // namespace retreat } // namespace retreat
@@ -61,7 +63,7 @@ CircularIndicatorManager::CircularIndicatorManager(QObject* parent)
, m_rotation(0) , m_rotation(0)
, m_completeEndProgress(0) { , m_completeEndProgress(0) {
// Fast out slow in // Fast out slow in
m_curve.addCubicBezierSegment({ 0.4, 0.0 }, { 0.2, 1.0 }, { 1.0, 1.0 }); m_curve.addCubicBezierSegment({0.4, 0.0}, {0.2, 1.0}, {1.0, 1.0});
} }
qreal CircularIndicatorManager::startFraction() const { qreal CircularIndicatorManager::startFraction() const {
@@ -87,24 +89,24 @@ void CircularIndicatorManager::setProgress(qreal progress) {
qreal CircularIndicatorManager::duration() const { qreal CircularIndicatorManager::duration() const {
if (m_type == IndeterminateAnimationType::Advance) { if (m_type == IndeterminateAnimationType::Advance) {
return advance::TOTAL_DURATION_IN_MS; return advance::TOTAL_DURATION_IN_MS;
} else {
return retreat::TOTAL_DURATION_IN_MS;
} }
return retreat::TOTAL_DURATION_IN_MS;
} }
qreal CircularIndicatorManager::completeEndDuration() const { qreal CircularIndicatorManager::completeEndDuration() const {
if (m_type == IndeterminateAnimationType::Advance) { if (m_type == IndeterminateAnimationType::Advance) {
return advance::DURATION_TO_COMPLETE_END_IN_MS; return advance::DURATION_TO_COMPLETE_END_IN_MS;
} else {
return retreat::DURATION_TO_COMPLETE_END_IN_MS;
} }
return retreat::DURATION_TO_COMPLETE_END_IN_MS;
} }
CircularIndicatorManager::IndeterminateAnimationType CircularIndicatorManager::indeterminateAnimationType() const { CircularIndicatorManager::IndeterminateAnimationType
CircularIndicatorManager::indeterminateAnimationType() const {
return m_type; return m_type;
} }
void CircularIndicatorManager::setIndeterminateAnimationType(IndeterminateAnimationType t) { void CircularIndicatorManager::setIndeterminateAnimationType(
IndeterminateAnimationType t) {
if (m_type != t) { if (m_type != t) {
m_type = t; m_type = t;
emit indeterminateAnimationTypeChanged(); emit indeterminateAnimationTypeChanged();
@@ -150,24 +152,27 @@ void CircularIndicatorManager::updateRetreat(qreal progress) {
// Extra rotation for the faster spinning. // Extra rotation for the faster spinning.
qreal spinRotation = 0; qreal spinRotation = 0;
for (const int spinDelay : DELAY_SPINS_IN_MS) { for (const int spinDelay : DELAY_SPINS_IN_MS) {
spinRotation += m_curve.valueForProgress(getFractionInRange(playtime, spinDelay, DURATION_SPIN_IN_MS)) * spinRotation +=
SPIN_ROTATION_DEGREES; m_curve.valueForProgress(
getFractionInRange(playtime, spinDelay, DURATION_SPIN_IN_MS)) *
SPIN_ROTATION_DEGREES;
} }
m_rotation = constantRotation + spinRotation; m_rotation = constantRotation + spinRotation;
emit rotationChanged(); emit rotationChanged();
// Grow active indicator. // Grow active indicator.
qreal fraction = qreal fraction = m_curve.valueForProgress(getFractionInRange(
m_curve.valueForProgress(getFractionInRange(playtime, DELAY_GROW_ACTIVE_IN_MS, DURATION_GROW_ACTIVE_IN_MS)); playtime, DELAY_GROW_ACTIVE_IN_MS, DURATION_GROW_ACTIVE_IN_MS));
fraction -= fraction -= m_curve.valueForProgress(getFractionInRange(
m_curve.valueForProgress(getFractionInRange(playtime, DELAY_SHRINK_ACTIVE_IN_MS, DURATION_SHRINK_ACTIVE_IN_MS)); playtime, DELAY_SHRINK_ACTIVE_IN_MS, DURATION_SHRINK_ACTIVE_IN_MS));
if (!qFuzzyIsNull(m_startFraction)) { if (!qFuzzyIsNull(m_startFraction)) {
m_startFraction = 0.0; m_startFraction = 0.0;
emit startFractionChanged(); emit startFractionChanged();
} }
const auto oldEndFrac = m_endFraction; const auto oldEndFrac = m_endFraction;
m_endFraction = std::lerp(END_FRACTION_RANGE[0], END_FRACTION_RANGE[1], fraction); m_endFraction =
std::lerp(END_FRACTION_RANGE[0], END_FRACTION_RANGE[1], fraction);
// Completing animation. // Completing animation.
if (m_completeEndProgress > 0) { if (m_completeEndProgress > 0) {
@@ -184,22 +189,32 @@ void CircularIndicatorManager::updateAdvance(qreal progress) {
const auto playtime = progress * TOTAL_DURATION_IN_MS; const auto playtime = progress * TOTAL_DURATION_IN_MS;
// Adds constant rotation to segment positions. // Adds constant rotation to segment positions.
m_startFraction = CONSTANT_ROTATION_DEGREES * progress + TAIL_DEGREES_OFFSET; m_startFraction =
CONSTANT_ROTATION_DEGREES * progress + TAIL_DEGREES_OFFSET;
m_endFraction = CONSTANT_ROTATION_DEGREES * progress; m_endFraction = CONSTANT_ROTATION_DEGREES * progress;
// Adds cycle specific rotation to segment positions. // Adds cycle specific rotation to segment positions.
for (size_t cycleIndex = 0; cycleIndex < TOTAL_CYCLES; ++cycleIndex) { for (size_t cycleIndex = 0; cycleIndex < TOTAL_CYCLES; ++cycleIndex) {
// While expanding. // While expanding.
qreal fraction = getFractionInRange(playtime, DELAY_TO_EXPAND_IN_MS[cycleIndex], DURATION_TO_EXPAND_IN_MS); qreal fraction = getFractionInRange(
m_endFraction += m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE; playtime,
DELAY_TO_EXPAND_IN_MS[cycleIndex],
DURATION_TO_EXPAND_IN_MS);
m_endFraction +=
m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE;
// While collapsing. // While collapsing.
fraction = getFractionInRange(playtime, DELAY_TO_COLLAPSE_IN_MS[cycleIndex], DURATION_TO_COLLAPSE_IN_MS); fraction = getFractionInRange(
m_startFraction += m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE; playtime,
DELAY_TO_COLLAPSE_IN_MS[cycleIndex],
DURATION_TO_COLLAPSE_IN_MS);
m_startFraction +=
m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE;
} }
// Closes the gap between head and tail for complete end. // Closes the gap between head and tail for complete end.
m_startFraction += (m_endFraction - m_startFraction) * m_completeEndProgress; m_startFraction +=
(m_endFraction - m_startFraction) * m_completeEndProgress;
m_startFraction /= 360.0; m_startFraction /= 360.0;
m_endFraction /= 360.0; m_endFraction /= 360.0;
@@ -1,6 +1,5 @@
#pragma once #pragma once
#include <cstdint>
#include <qeasingcurve.h> #include <qeasingcurve.h>
#include <qobject.h> #include <qobject.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
@@ -8,66 +7,71 @@
namespace ZShell::internal { namespace ZShell::internal {
class CircularIndicatorManager : public QObject { class CircularIndicatorManager : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(qreal startFraction READ startFraction NOTIFY startFractionChanged) Q_PROPERTY(
Q_PROPERTY(qreal endFraction READ endFraction NOTIFY endFractionChanged) qreal startFraction READ startFraction NOTIFY startFractionChanged)
Q_PROPERTY(qreal rotation READ rotation NOTIFY rotationChanged) Q_PROPERTY(qreal endFraction READ endFraction NOTIFY endFractionChanged)
Q_PROPERTY(qreal progress READ progress WRITE setProgress NOTIFY progressChanged) Q_PROPERTY(qreal rotation READ rotation NOTIFY rotationChanged)
Q_PROPERTY(qreal completeEndProgress READ completeEndProgress WRITE setCompleteEndProgress NOTIFY Q_PROPERTY(
completeEndProgressChanged) qreal progress READ progress WRITE setProgress NOTIFY progressChanged)
Q_PROPERTY(qreal duration READ duration NOTIFY indeterminateAnimationTypeChanged) Q_PROPERTY(
Q_PROPERTY(qreal completeEndDuration READ completeEndDuration NOTIFY indeterminateAnimationTypeChanged) qreal completeEndProgress READ completeEndProgress WRITE
Q_PROPERTY(IndeterminateAnimationType indeterminateAnimationType READ indeterminateAnimationType WRITE setCompleteEndProgress NOTIFY completeEndProgressChanged)
setIndeterminateAnimationType NOTIFY indeterminateAnimationTypeChanged) Q_PROPERTY(
qreal duration READ duration NOTIFY indeterminateAnimationTypeChanged)
Q_PROPERTY(
qreal completeEndDuration READ completeEndDuration NOTIFY
indeterminateAnimationTypeChanged)
Q_PROPERTY(
IndeterminateAnimationType indeterminateAnimationType READ
indeterminateAnimationType WRITE setIndeterminateAnimationType
NOTIFY indeterminateAnimationTypeChanged)
public: public:
explicit CircularIndicatorManager(QObject* parent = nullptr); explicit CircularIndicatorManager(QObject* parent = nullptr);
enum IndeterminateAnimationType { enum IndeterminateAnimationType { Advance = 0, Retreat };
Advance = 0, Q_ENUM(IndeterminateAnimationType)
Retreat
};
Q_ENUM(IndeterminateAnimationType)
[[nodiscard]] qreal startFraction() const; [[nodiscard]] qreal startFraction() const;
[[nodiscard]] qreal endFraction() const; [[nodiscard]] qreal endFraction() const;
[[nodiscard]] qreal rotation() const; [[nodiscard]] qreal rotation() const;
[[nodiscard]] qreal progress() const; [[nodiscard]] qreal progress() const;
void setProgress(qreal progress); void setProgress(qreal progress);
[[nodiscard]] qreal completeEndProgress() const; [[nodiscard]] qreal completeEndProgress() const;
void setCompleteEndProgress(qreal progress); void setCompleteEndProgress(qreal progress);
[[nodiscard]] qreal duration() const; [[nodiscard]] qreal duration() const;
[[nodiscard]] qreal completeEndDuration() const; [[nodiscard]] qreal completeEndDuration() const;
[[nodiscard]] IndeterminateAnimationType indeterminateAnimationType() const; [[nodiscard]] IndeterminateAnimationType indeterminateAnimationType() const;
void setIndeterminateAnimationType(IndeterminateAnimationType t); void setIndeterminateAnimationType(IndeterminateAnimationType t);
signals: signals:
void startFractionChanged(); void startFractionChanged();
void endFractionChanged(); void endFractionChanged();
void rotationChanged(); void rotationChanged();
void progressChanged(); void progressChanged();
void completeEndProgressChanged(); void completeEndProgressChanged();
void indeterminateAnimationTypeChanged(); void indeterminateAnimationTypeChanged();
private: private:
IndeterminateAnimationType m_type; IndeterminateAnimationType m_type;
QEasingCurve m_curve; QEasingCurve m_curve;
qreal m_progress; qreal m_progress;
qreal m_startFraction; qreal m_startFraction;
qreal m_endFraction; qreal m_endFraction;
qreal m_rotation; qreal m_rotation;
qreal m_completeEndProgress; qreal m_completeEndProgress;
void update(qreal progress); void update(qreal progress);
void updateAdvance(qreal progress); void updateAdvance(qreal progress);
void updateRetreat(qreal progress); void updateRetreat(qreal progress);
}; };
} // namespace ZShell::internal } // namespace ZShell::internal
+82 -81
View File
@@ -5,130 +5,131 @@
namespace ZShell::internal::hypr { namespace ZShell::internal::hypr {
HyprKeyboard::HyprKeyboard(QJsonObject ipcObject, QObject* parent) HyprKeyboard::HyprKeyboard(QJsonObject ipcObject, QObject* parent)
: QObject(parent) : QObject(parent), m_lastIpcObject(ipcObject) {}
, m_lastIpcObject(ipcObject) {}
QVariantHash HyprKeyboard::lastIpcObject() const { QVariantHash HyprKeyboard::lastIpcObject() const {
return m_lastIpcObject.toVariantHash(); return m_lastIpcObject.toVariantHash();
} }
QString HyprKeyboard::address() const { QString HyprKeyboard::address() const {
return m_lastIpcObject.value("address").toString(); return m_lastIpcObject.value("address").toString();
} }
QString HyprKeyboard::name() const { QString HyprKeyboard::name() const {
return m_lastIpcObject.value("name").toString(); return m_lastIpcObject.value("name").toString();
} }
QString HyprKeyboard::layout() const { QString HyprKeyboard::layout() const {
return m_lastIpcObject.value("layout").toString(); return m_lastIpcObject.value("layout").toString();
} }
QString HyprKeyboard::activeKeymap() const { QString HyprKeyboard::activeKeymap() const {
return m_lastIpcObject.value("active_keymap").toString(); return m_lastIpcObject.value("active_keymap").toString();
} }
bool HyprKeyboard::capsLock() const { bool HyprKeyboard::capsLock() const {
return m_lastIpcObject.value("capsLock").toBool(); return m_lastIpcObject.value("capsLock").toBool();
} }
bool HyprKeyboard::numLock() const { bool HyprKeyboard::numLock() const {
return m_lastIpcObject.value("numLock").toBool(); return m_lastIpcObject.value("numLock").toBool();
} }
bool HyprKeyboard::main() const { bool HyprKeyboard::main() const {
return m_lastIpcObject.value("main").toBool(); return m_lastIpcObject.value("main").toBool();
} }
bool HyprKeyboard::updateLastIpcObject(QJsonObject object) { bool HyprKeyboard::updateLastIpcObject(QJsonObject object) {
if (m_lastIpcObject == object) { if (m_lastIpcObject == object) {
return false; return false;
} }
const auto last = m_lastIpcObject; const auto last = m_lastIpcObject;
m_lastIpcObject = object; m_lastIpcObject = object;
emit lastIpcObjectChanged(); emit lastIpcObjectChanged();
bool dirty = false; bool dirty = false;
if (last.value("address") != object.value("address")) { if (last.value("address") != object.value("address")) {
dirty = true; dirty = true;
emit addressChanged(); emit addressChanged();
} }
if (last.value("name") != object.value("name")) { if (last.value("name") != object.value("name")) {
dirty = true; dirty = true;
emit nameChanged(); emit nameChanged();
} }
if (last.value("layout") != object.value("layout")) { if (last.value("layout") != object.value("layout")) {
dirty = true; dirty = true;
emit layoutChanged(); emit layoutChanged();
} }
if (last.value("active_keymap") != object.value("active_keymap")) { if (last.value("active_keymap") != object.value("active_keymap")) {
dirty = true; dirty = true;
emit activeKeymapChanged(); emit activeKeymapChanged();
} }
if (last.value("capsLock") != object.value("capsLock")) { if (last.value("capsLock") != object.value("capsLock")) {
dirty = true; dirty = true;
emit capsLockChanged(); emit capsLockChanged();
} }
if (last.value("numLock") != object.value("numLock")) { if (last.value("numLock") != object.value("numLock")) {
dirty = true; dirty = true;
emit numLockChanged(); emit numLockChanged();
} }
if (last.value("main") != object.value("main")) { if (last.value("main") != object.value("main")) {
dirty = true; dirty = true;
emit mainChanged(); emit mainChanged();
} }
return dirty; return dirty;
} }
HyprDevices::HyprDevices(QObject* parent) HyprDevices::HyprDevices(QObject* parent) : QObject(parent) {}
: QObject(parent) {}
QQmlListProperty<HyprKeyboard> HyprDevices::keyboards() { QQmlListProperty<HyprKeyboard> HyprDevices::keyboards() {
return QQmlListProperty<HyprKeyboard>(this, &m_keyboards); return QQmlListProperty<HyprKeyboard>(this, &m_keyboards);
} }
bool HyprDevices::updateLastIpcObject(QJsonObject object) { bool HyprDevices::updateLastIpcObject(QJsonObject object) {
const auto val = object.value("keyboards").toArray(); const auto val = object.value("keyboards").toArray();
bool dirty = false; bool dirty = false;
for (auto it = m_keyboards.begin(); it != m_keyboards.end();) { for (auto it = m_keyboards.begin(); it != m_keyboards.end();) {
auto* const keyboard = *it; auto* const keyboard = *it;
const auto inNewValues = std::any_of(val.begin(), val.end(), [keyboard](const QJsonValue& o) { const auto inNewValues =
return o.toObject().value("address").toString() == keyboard->address(); std::any_of(val.begin(), val.end(), [keyboard](const QJsonValue& o) {
}); return o.toObject().value("address").toString() ==
keyboard->address();
});
if (!inNewValues) { if (!inNewValues) {
dirty = true; dirty = true;
it = m_keyboards.erase(it); it = m_keyboards.erase(it);
keyboard->deleteLater(); keyboard->deleteLater();
} else { } else {
++it; ++it;
} }
} }
for (const auto& o : val) { for (const auto& o : val) {
const auto obj = o.toObject(); const auto obj = o.toObject();
const auto addr = obj.value("address").toString(); const auto addr = obj.value("address").toString();
auto it = std::find_if(m_keyboards.begin(), m_keyboards.end(), [addr](const HyprKeyboard* kb) { auto it = std::find_if(
return kb->address() == addr; m_keyboards.begin(),
}); m_keyboards.end(),
[addr](const HyprKeyboard* kb) { return kb->address() == addr; });
if (it != m_keyboards.end()) { if (it != m_keyboards.end()) {
dirty |= (*it)->updateLastIpcObject(obj); dirty |= (*it)->updateLastIpcObject(obj);
} else { } else {
dirty = true; dirty = true;
m_keyboards << new HyprKeyboard(obj, this); m_keyboards << new HyprKeyboard(obj, this);
} }
} }
if (dirty) { if (dirty) {
emit keyboardsChanged(); emit keyboardsChanged();
} }
return dirty; return dirty;
} }
} // namespace ZShell::internal::hypr } // namespace ZShell::internal::hypr
+51 -46
View File
@@ -8,67 +8,72 @@
namespace ZShell::internal::hypr { namespace ZShell::internal::hypr {
class HyprKeyboard : public QObject { class HyprKeyboard : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("HyprKeyboard instances can only be retrieved from a HyprDevices") QML_UNCREATABLE(
"HyprKeyboard instances can only be retrieved from a HyprDevices")
Q_PROPERTY(QVariantHash lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged) Q_PROPERTY(
Q_PROPERTY(QString address READ address NOTIFY addressChanged) QVariantHash lastIpcObject READ lastIpcObject NOTIFY
Q_PROPERTY(QString name READ name NOTIFY nameChanged) lastIpcObjectChanged)
Q_PROPERTY(QString layout READ layout NOTIFY layoutChanged) Q_PROPERTY(QString address READ address NOTIFY addressChanged)
Q_PROPERTY(QString activeKeymap READ activeKeymap NOTIFY activeKeymapChanged) Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(bool capsLock READ capsLock NOTIFY capsLockChanged) Q_PROPERTY(QString layout READ layout NOTIFY layoutChanged)
Q_PROPERTY(bool numLock READ numLock NOTIFY numLockChanged) Q_PROPERTY(QString activeKeymap READ activeKeymap NOTIFY activeKeymapChanged)
Q_PROPERTY(bool main READ main NOTIFY mainChanged) Q_PROPERTY(bool capsLock READ capsLock NOTIFY capsLockChanged)
Q_PROPERTY(bool numLock READ numLock NOTIFY numLockChanged)
Q_PROPERTY(bool main READ main NOTIFY mainChanged)
public: public:
explicit HyprKeyboard(QJsonObject ipcObject, QObject* parent = nullptr); explicit HyprKeyboard(QJsonObject ipcObject, QObject* parent = nullptr);
[[nodiscard]] QVariantHash lastIpcObject() const; [[nodiscard]] QVariantHash lastIpcObject() const;
[[nodiscard]] QString address() const; [[nodiscard]] QString address() const;
[[nodiscard]] QString name() const; [[nodiscard]] QString name() const;
[[nodiscard]] QString layout() const; [[nodiscard]] QString layout() const;
[[nodiscard]] QString activeKeymap() const; [[nodiscard]] QString activeKeymap() const;
[[nodiscard]] bool capsLock() const; [[nodiscard]] bool capsLock() const;
[[nodiscard]] bool numLock() const; [[nodiscard]] bool numLock() const;
[[nodiscard]] bool main() const; [[nodiscard]] bool main() const;
bool updateLastIpcObject(QJsonObject object); bool updateLastIpcObject(QJsonObject object);
signals: signals:
void lastIpcObjectChanged(); void lastIpcObjectChanged();
void addressChanged(); void addressChanged();
void nameChanged(); void nameChanged();
void layoutChanged(); void layoutChanged();
void activeKeymapChanged(); void activeKeymapChanged();
void capsLockChanged(); void capsLockChanged();
void numLockChanged(); void numLockChanged();
void mainChanged(); void mainChanged();
private: private:
QJsonObject m_lastIpcObject; QJsonObject m_lastIpcObject;
}; };
class HyprDevices : public QObject { class HyprDevices : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("HyprDevices instances can only be retrieved from a HyprExtras") QML_UNCREATABLE(
"HyprDevices instances can only be retrieved from a HyprExtras")
Q_PROPERTY( Q_PROPERTY(
QQmlListProperty<ZShell::internal::hypr::HyprKeyboard> keyboards READ keyboards NOTIFY keyboardsChanged) QQmlListProperty<ZShell::internal::hypr::HyprKeyboard> keyboards READ
keyboards NOTIFY keyboardsChanged)
public: public:
explicit HyprDevices(QObject* parent = nullptr); explicit HyprDevices(QObject* parent = nullptr);
[[nodiscard]] QQmlListProperty<HyprKeyboard> keyboards(); [[nodiscard]] QQmlListProperty<HyprKeyboard> keyboards();
bool updateLastIpcObject(QJsonObject object); bool updateLastIpcObject(QJsonObject object);
signals: signals:
void keyboardsChanged(); void keyboardsChanged();
private: private:
QList<HyprKeyboard*> m_keyboards; QList<HyprKeyboard*> m_keyboards;
}; };
} // namespace ZShell::internal::hypr } // namespace ZShell::internal::hypr
+112 -59
View File
@@ -56,6 +56,7 @@ static QString luaEscapeString(const QString& s) {
static QString luaValue(const QVariant& v); static QString luaValue(const QVariant& v);
// NOLINTNEXTLINE(misc-no-recursion)
static QString luaArray(const QVariantList& list) { static QString luaArray(const QVariantList& list) {
QStringList parts; QStringList parts;
parts.reserve(list.size()); parts.reserve(list.size());
@@ -64,7 +65,8 @@ static QString luaArray(const QVariantList& list) {
parts << luaValue(item); parts << luaValue(item);
} }
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }"); return QLatin1String("{ ") + parts.join(QLatin1String(", ")) +
QLatin1String(" }");
} }
static QString luaArray(const QStringList& list) { static QString luaArray(const QStringList& list) {
@@ -75,31 +77,39 @@ static QString luaArray(const QStringList& list) {
parts << luaEscapeString(item); parts << luaEscapeString(item);
} }
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }"); return QLatin1String("{ ") + parts.join(QLatin1String(", ")) +
QLatin1String(" }");
} }
// NOLINTNEXTLINE(misc-no-recursion)
static QString luaMapFromHash(const QVariantHash& hash) { static QString luaMapFromHash(const QVariantHash& hash) {
QStringList parts; QStringList parts;
parts.reserve(hash.size()); parts.reserve(hash.size());
for (auto it = hash.cbegin(); it != hash.cend(); ++it) { for (auto it = hash.cbegin(); it != hash.cend(); ++it) {
parts << luaEscapeString(it.key()) + QLatin1String(" = ") + luaValue(it.value()); parts << luaEscapeString(it.key()) + QLatin1String(" = ") +
luaValue(it.value());
} }
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }"); return QLatin1String("{ ") + parts.join(QLatin1String(", ")) +
QLatin1String(" }");
} }
// NOLINTNEXTLINE(misc-no-recursion)
static QString luaMap(const QVariantMap& map) { static QString luaMap(const QVariantMap& map) {
QStringList parts; QStringList parts;
parts.reserve(map.size()); parts.reserve(map.size());
for (auto it = map.cbegin(); it != map.cend(); ++it) { for (auto it = map.cbegin(); it != map.cend(); ++it) {
parts << luaEscapeString(it.key()) + QLatin1String(" = ") + luaValue(it.value()); parts << luaEscapeString(it.key()) + QLatin1String(" = ") +
luaValue(it.value());
} }
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }"); return QLatin1String("{ ") + parts.join(QLatin1String(", ")) +
QLatin1String(" }");
} }
// NOLINTNEXTLINE(misc-no-recursion)
static QString luaValue(const QVariant& v) { static QString luaValue(const QVariant& v) {
if (!v.isValid() || v.isNull()) { if (!v.isValid() || v.isNull()) {
return QLatin1String("nil"); return QLatin1String("nil");
@@ -143,7 +153,8 @@ static QString normalizeOptionPath(QString key) {
} }
static QString buildHlConfigCall(const QString& key, const QVariant& value) { static QString buildHlConfigCall(const QString& key, const QVariant& value) {
const auto parts = normalizeOptionPath(key).split(QLatin1Char('.'), Qt::SkipEmptyParts); const auto parts =
normalizeOptionPath(key).split(QLatin1Char('.'), Qt::SkipEmptyParts);
if (parts.isEmpty()) { if (parts.isEmpty()) {
return {}; return {};
} }
@@ -189,7 +200,8 @@ static QVariant parseGetOptionValue(const QJsonObject& obj) {
const auto option = obj.value(QStringLiteral("option")).toString(); const auto option = obj.value(QStringLiteral("option")).toString();
if (option.contains(QStringLiteral("color")) || option.contains(QStringLiteral("col."))) { if (option.contains(QStringLiteral("color")) ||
option.contains(QStringLiteral("col."))) {
return colorFromInt(static_cast<quint32>(value)); return colorFromInt(static_cast<quint32>(value));
} }
@@ -234,7 +246,9 @@ static QVariant parseGetOptionValue(const QJsonObject& obj) {
return {}; return {};
} }
static void insertNestedValue(QVariantMap& root, const QStringList& path, const QVariant& value) { // NOLINTNEXTLINE(misc-no-recursion)
static void insertNestedValue(
QVariantMap& root, const QStringList& path, const QVariant& value) {
if (path.isEmpty()) { if (path.isEmpty()) {
return; return;
} }
@@ -261,16 +275,19 @@ HyprExtras::HyprExtras(QObject* parent)
, m_devices(new HyprDevices(this)) { , m_devices(new HyprDevices(this)) {
const auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE"); const auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE");
if (his.isEmpty()) { if (his.isEmpty()) {
qCWarning(lcHypr) << "$HYPRLAND_INSTANCE_SIGNATURE is unset. Unable to connect to Hyprland socket."; qCWarning(lcHypr) << "$HYPRLAND_INSTANCE_SIGNATURE is unset. Unable to "
"connect to Hyprland socket.";
return; return;
} }
auto hyprDir = QString("%1/hypr/%2").arg(qEnvironmentVariable("XDG_RUNTIME_DIR"), his); auto hyprDir =
QString("%1/hypr/%2").arg(qEnvironmentVariable("XDG_RUNTIME_DIR"), his);
if (!QDir(hyprDir).exists()) { if (!QDir(hyprDir).exists()) {
hyprDir = QStringLiteral("/tmp/hypr/") + his; hyprDir = QStringLiteral("/tmp/hypr/") + his;
if (!QDir(hyprDir).exists()) { if (!QDir(hyprDir).exists()) {
qCWarning(lcHypr) << "Hyprland socket directory does not exist. Unable to connect to Hyprland socket."; qCWarning(lcHypr) << "Hyprland socket directory does not exist. "
"Unable to connect to Hyprland socket.";
return; return;
} }
} }
@@ -283,9 +300,15 @@ HyprExtras::HyprExtras(QObject* parent)
m_socket = new QLocalSocket(this); m_socket = new QLocalSocket(this);
QObject::connect(m_socket, &QLocalSocket::errorOccurred, this, &HyprExtras::socketError); QObject::connect(
QObject::connect(m_socket, &QLocalSocket::stateChanged, this, &HyprExtras::socketStateChanged); m_socket, &QLocalSocket::errorOccurred, this, &HyprExtras::socketError);
QObject::connect(m_socket, &QLocalSocket::readyRead, this, &HyprExtras::readEvent); QObject::connect(
m_socket,
&QLocalSocket::stateChanged,
this,
&HyprExtras::socketStateChanged);
QObject::connect(
m_socket, &QLocalSocket::readyRead, this, &HyprExtras::readEvent);
m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly); m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly);
} }
@@ -304,10 +327,11 @@ void HyprExtras::message(const QString& message) {
} }
makeRequest(message, [](bool success, const QByteArray& res) { makeRequest(message, [](bool success, const QByteArray& res) {
if (!success) { if (!success) {
qCWarning(lcHypr) << "message: request error:" << QString::fromUtf8(res); qCWarning(lcHypr)
} << "message: request error:" << QString::fromUtf8(res);
}); }
});
} }
void HyprExtras::batchMessage(const QStringList& messages) { void HyprExtras::batchMessage(const QStringList& messages) {
@@ -315,10 +339,12 @@ void HyprExtras::batchMessage(const QStringList& messages) {
return; return;
} }
makeRequest(QStringLiteral("[[BATCH]]") + messages.join(QLatin1Char(';')), makeRequest(
[](bool success, const QByteArray& res) { QStringLiteral("[[BATCH]]") + messages.join(QLatin1Char(';')),
[](bool success, const QByteArray& res) {
if (!success) { if (!success) {
qCWarning(lcHypr) << "batchMessage: request error:" << QString::fromUtf8(res); qCWarning(lcHypr)
<< "batchMessage: request error:" << QString::fromUtf8(res);
} }
}); });
} }
@@ -342,11 +368,14 @@ void HyprExtras::applyOptions(const QVariantHash& options) {
return; return;
} }
makeRequest(QStringLiteral("eval ") + calls.join(QLatin1String("; ")), [this](bool success, const QByteArray& res) { makeRequest(
QStringLiteral("eval ") + calls.join(QLatin1String("; ")),
[this](bool success, const QByteArray& res) {
if (success) { if (success) {
refreshOptions(); refreshOptions();
} else { } else {
qCWarning(lcHypr) << "applyOptions: request error" << QString::fromUtf8(res); qCWarning(lcHypr)
<< "applyOptions: request error" << QString::fromUtf8(res);
} }
}); });
} }
@@ -372,26 +401,26 @@ void HyprExtras::refreshOptions() {
auto nextOptions = std::make_shared<QVariantMap>(); auto nextOptions = std::make_shared<QVariantMap>();
auto step = std::make_shared<std::function<void(int)> >(); auto step = std::make_shared<std::function<void(int)>>();
*step = [this, generation, nextOptions, step](int index) { *step = [this, generation, nextOptions, step](int index) {
if (generation != m_optionsRefreshGeneration) { if (generation != m_optionsRefreshGeneration) {
return; return;
}
if (index >= optionKeys.size()) {
if (m_options != *nextOptions) {
m_options = *nextOptions;
emit optionsChanged();
} }
return;
}
if (index >= optionKeys.size()) { const QString key = optionKeys.at(index);
if (m_options != *nextOptions) {
m_options = *nextOptions;
emit optionsChanged();
}
return;
}
const QString key = optionKeys.at(index); m_optionsRefresh = makeRequestJson(
QStringLiteral("getoption ") + key,
m_optionsRefresh = makeRequestJson( [this, generation, nextOptions, step, index, key](
QStringLiteral("getoption ") + key, bool success, const QJsonDocument& response) {
[this, generation, nextOptions, step, index, key](bool success, const QJsonDocument& response)
{
m_optionsRefresh.reset(); m_optionsRefresh.reset();
if (generation != m_optionsRefreshGeneration) { if (generation != m_optionsRefreshGeneration) {
@@ -399,19 +428,26 @@ void HyprExtras::refreshOptions() {
} }
if (success && response.isObject()) { if (success && response.isObject()) {
const QVariant value = parseGetOptionValue(response.object()); const QVariant value =
parseGetOptionValue(response.object());
if (value.isValid()) { if (value.isValid()) {
insertNestedValue(*nextOptions, key.split(QLatin1Char(':'), Qt::SkipEmptyParts), value); insertNestedValue(
*nextOptions,
key.split(QLatin1Char(':'), Qt::SkipEmptyParts),
value);
} else { } else {
qCWarning(lcHypr) << "refreshOptions: getoption returned no usable value for" << key; qCWarning(lcHypr) << "refreshOptions: getoption "
"returned no usable value for"
<< key;
} }
} else if (!success) { } else if (!success) {
qCWarning(lcHypr) << "refreshOptions: getoption request error for" << key; qCWarning(lcHypr)
<< "refreshOptions: getoption request error for" << key;
} }
(*step)(index + 1); (*step)(index + 1);
}); });
}; };
(*step)(0); (*step)(0);
} }
@@ -421,7 +457,9 @@ void HyprExtras::refreshDevices() {
m_devicesRefresh->close(); m_devicesRefresh->close();
} }
m_devicesRefresh = makeRequestJson(QStringLiteral("devices"), [this](bool success, const QJsonDocument& response) { m_devicesRefresh = makeRequestJson(
QStringLiteral("devices"),
[this](bool success, const QJsonDocument& response) {
m_devicesRefresh.reset(); m_devicesRefresh.reset();
if (success) { if (success) {
m_devices->updateLastIpcObject(response.object()); m_devices->updateLastIpcObject(response.object());
@@ -431,15 +469,19 @@ void HyprExtras::refreshDevices() {
void HyprExtras::socketError(QLocalSocket::LocalSocketError error) const { void HyprExtras::socketError(QLocalSocket::LocalSocketError error) const {
if (!m_socketValid) { if (!m_socketValid) {
qCWarning(lcHypr) << "socketError: unable to connect to Hyprland event socket:" << error; qCWarning(lcHypr)
<< "socketError: unable to connect to Hyprland event socket:"
<< error;
} else { } else {
qCWarning(lcHypr) << "socketError: Hyprland event socket error:" << error; qCWarning(lcHypr) << "socketError: Hyprland event socket error:"
<< error;
} }
} }
void HyprExtras::socketStateChanged(QLocalSocket::LocalSocketState state) { void HyprExtras::socketStateChanged(QLocalSocket::LocalSocketState state) {
if (state == QLocalSocket::UnconnectedState && m_socketValid) { if (state == QLocalSocket::UnconnectedState && m_socketValid) {
qCWarning(lcHypr) << "socketStateChanged: Hyprland event socket disconnected."; qCWarning(lcHypr)
<< "socketStateChanged: Hyprland event socket disconnected.";
} }
m_socketValid = state == QLocalSocket::ConnectedState; m_socketValid = state == QLocalSocket::ConnectedState;
@@ -452,7 +494,8 @@ void HyprExtras::readEvent() {
break; break;
} }
rawEvent.truncate(rawEvent.length() - 1); rawEvent.truncate(rawEvent.length() - 1);
const auto event = QByteArrayView(rawEvent.data(), rawEvent.indexOf(">>")); const auto event =
QByteArrayView(rawEvent.data(), rawEvent.indexOf(">>"));
handleEvent(QString::fromUtf8(event)); handleEvent(QString::fromUtf8(event));
} }
} }
@@ -466,14 +509,18 @@ void HyprExtras::handleEvent(const QString& event) {
} }
HyprExtras::SocketPtr HyprExtras::makeRequestJson( HyprExtras::SocketPtr HyprExtras::makeRequestJson(
const QString& request, const std::function<void(bool, QJsonDocument)>& callback) { const QString& request,
return makeRequest(QStringLiteral("j/") + request, [callback](bool success, const QByteArray& response) { const std::function<void(bool, QJsonDocument)>& callback) {
return makeRequest(
QStringLiteral("j/") + request,
[callback](bool success, const QByteArray& response) {
callback(success, QJsonDocument::fromJson(response)); callback(success, QJsonDocument::fromJson(response));
}); });
} }
HyprExtras::SocketPtr HyprExtras::makeRequest( HyprExtras::SocketPtr HyprExtras::makeRequest(
const QString& request, const std::function<void(bool, QByteArray)>& callback) { const QString& request,
const std::function<void(bool, QByteArray)>& callback) {
if (m_requestSocket.isEmpty()) { if (m_requestSocket.isEmpty()) {
return SocketPtr(); return SocketPtr();
} }
@@ -481,18 +528,24 @@ HyprExtras::SocketPtr HyprExtras::makeRequest(
auto socket = SocketPtr::create(this); auto socket = SocketPtr::create(this);
QObject::connect(socket.data(), &QLocalSocket::connected, this, [=, this]() { QObject::connect(socket.data(), &QLocalSocket::connected, this, [=, this]() {
QObject::connect(socket.data(), &QLocalSocket::readyRead, this, [socket, callback]() { QObject::connect(
socket.data(), &QLocalSocket::readyRead, this, [socket, callback]() {
const auto response = socket->readAll(); const auto response = socket->readAll();
callback(true, std::move(response)); callback(true, std::move(response));
socket->close(); socket->close();
}); });
socket->write(request.toUtf8()); socket->write(request.toUtf8());
socket->flush(); socket->flush();
}); });
QObject::connect(socket.data(), &QLocalSocket::errorOccurred, this, [=](QLocalSocket::LocalSocketError err) { QObject::connect(
qCWarning(lcHypr) << "makeRequest: error making request:" << err << "| request:" << request; socket.data(),
&QLocalSocket::errorOccurred,
this,
[=](QLocalSocket::LocalSocketError err) {
qCWarning(lcHypr) << "makeRequest: error making request:" << err
<< "| request:" << request;
callback(false, {}); callback(false, {});
socket->close(); socket->close();
}); });
+38 -33
View File
@@ -15,51 +15,56 @@ namespace ZShell::internal::hypr {
class HyprDevices; class HyprDevices;
class HyprExtras : public QObject { class HyprExtras : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_MOC_INCLUDE("hyprdevices.hpp") Q_MOC_INCLUDE("hyprdevices.hpp")
Q_PROPERTY(QVariantMap options READ options NOTIFY optionsChanged) Q_PROPERTY(QVariantMap options READ options NOTIFY optionsChanged)
Q_PROPERTY(ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT) Q_PROPERTY(
ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT)
public: public:
explicit HyprExtras(QObject* parent = nullptr); explicit HyprExtras(QObject* parent = nullptr);
[[nodiscard]] QVariantMap options() const; [[nodiscard]] QVariantMap options() const;
[[nodiscard]] HyprDevices* devices() const; [[nodiscard]] HyprDevices* devices() const;
Q_INVOKABLE void message(const QString& message); Q_INVOKABLE void message(const QString& message);
Q_INVOKABLE void batchMessage(const QStringList& messages); Q_INVOKABLE void batchMessage(const QStringList& messages);
Q_INVOKABLE void applyOptions(const QVariantHash& options); Q_INVOKABLE void applyOptions(const QVariantHash& options);
Q_INVOKABLE void refreshOptions(); Q_INVOKABLE void refreshOptions();
Q_INVOKABLE void refreshDevices(); Q_INVOKABLE void refreshDevices();
signals: signals:
void optionsChanged(); void optionsChanged();
private: private:
using SocketPtr = QSharedPointer<QLocalSocket>; using SocketPtr = QSharedPointer<QLocalSocket>;
QString m_requestSocket; QString m_requestSocket;
QString m_eventSocket; QString m_eventSocket;
QLocalSocket* m_socket; QLocalSocket* m_socket;
bool m_socketValid; bool m_socketValid;
QVariantMap m_options; QVariantMap m_options;
HyprDevices* const m_devices; HyprDevices* const m_devices;
SocketPtr m_optionsRefresh; SocketPtr m_optionsRefresh;
SocketPtr m_devicesRefresh; SocketPtr m_devicesRefresh;
quint64 m_optionsRefreshGeneration = 0; quint64 m_optionsRefreshGeneration = 0;
void socketError(QLocalSocket::LocalSocketError error) const; void socketError(QLocalSocket::LocalSocketError error) const;
void socketStateChanged(QLocalSocket::LocalSocketState state); void socketStateChanged(QLocalSocket::LocalSocketState state);
void readEvent(); void readEvent();
void handleEvent(const QString& event); void handleEvent(const QString& event);
SocketPtr makeRequestJson(const QString& request, const std::function<void(bool, QJsonDocument)>& callback); SocketPtr makeRequestJson(
SocketPtr makeRequest(const QString& request, const std::function<void(bool, QByteArray)>& callback); const QString& request,
const std::function<void(bool, QJsonDocument)>& callback);
SocketPtr makeRequest(
const QString& request,
const std::function<void(bool, QByteArray)>& callback);
}; };
} // namespace ZShell::internal::hypr } // namespace ZShell::internal::hypr
+31 -27
View File
@@ -14,27 +14,29 @@ LidWatcher::LidWatcher(QObject* parent) : QObject(parent) {
auto bus = QDBusConnection::systemBus(); auto bus = QDBusConnection::systemBus();
if (!bus.isConnected()) { if (!bus.isConnected()) {
qCWarning(lcLidWatcher) qCWarning(lcLidWatcher)
<< "Failed to connect to system bus:" << bus.lastError().message(); << "Failed to connect to system bus:" << bus.lastError().message();
return; return;
} }
bool ok = bus.connect("org.freedesktop.login1", bool ok = bus.connect(
"/org/freedesktop/login1", "org.freedesktop.login1",
"org.freedesktop.login1.Manager", "/org/freedesktop/login1",
"PrepareForSleep", "org.freedesktop.login1.Manager",
this, "PrepareForSleep",
SLOT(handlePrepareForSleep(bool))); this,
SLOT(handlePrepareForSleep(bool)));
if (!ok) { if (!ok) {
qCWarning(lcLidWatcher) qCWarning(lcLidWatcher)
<< "Failed to connect to PrepareForSleep signal:" << "Failed to connect to PrepareForSleep signal:"
<< bus.lastError().message(); << bus.lastError().message();
} }
QDBusInterface login1("org.freedesktop.login1", QDBusInterface login1(
"/org/freedesktop/login1", "org.freedesktop.login1",
"org.freedesktop.login1.Manager", "/org/freedesktop/login1",
bus); "org.freedesktop.login1.Manager",
bus);
const QDBusReply<QDBusObjectPath> reply = login1.call("GetSession", "auto"); const QDBusReply<QDBusObjectPath> reply = login1.call("GetSession", "auto");
if (!reply.isValid()) { if (!reply.isValid()) {
qCWarning(lcLidWatcher) << "Failed to get session path"; qCWarning(lcLidWatcher) << "Failed to get session path";
@@ -42,28 +44,30 @@ LidWatcher::LidWatcher(QObject* parent) : QObject(parent) {
} }
const auto sessionPath = reply.value().path(); const auto sessionPath = reply.value().path();
ok = bus.connect("org.freedesktop.login1", ok = bus.connect(
sessionPath, "org.freedesktop.login1",
"org.freedesktop.login1.Session", sessionPath,
"Lock", "org.freedesktop.login1.Session",
this, "Lock",
SLOT(handleLockRequested())); this,
SLOT(handleLockRequested()));
if (!ok) { if (!ok) {
qCWarning(lcLidWatcher) qCWarning(lcLidWatcher)
<< "Failed to connect to Lock signal:" << bus.lastError().message(); << "Failed to connect to Lock signal:" << bus.lastError().message();
} }
ok = bus.connect("org.freedesktop.login1", ok = bus.connect(
sessionPath, "org.freedesktop.login1",
"org.freedesktop.login1.Session", sessionPath,
"Unlock", "org.freedesktop.login1.Session",
this, "Unlock",
SLOT(handleUnlockRequested())); this,
SLOT(handleUnlockRequested()));
if (!ok) { if (!ok) {
qCWarning(lcLidWatcher) << "Failed to connect to Unlock signal:" qCWarning(lcLidWatcher) << "Failed to connect to Unlock signal:"
<< bus.lastError().message(); << bus.lastError().message();
} }
} }
+13 -13
View File
@@ -6,22 +6,22 @@
namespace ZShell::internal { namespace ZShell::internal {
class LidWatcher : public QObject { class LidWatcher : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
public: public:
explicit LidWatcher(QObject* parent = nullptr); explicit LidWatcher(QObject* parent = nullptr);
signals: signals:
void aboutToSleep(); void aboutToSleep();
void resumed(); void resumed();
void lockRequested(); void lockRequested();
void unlockRequested(); void unlockRequested();
private slots: private slots:
void handlePrepareForSleep(bool sleep); void handlePrepareForSleep(bool sleep);
void handleLockRequested(); void handleLockRequested();
void handleUnlockRequested(); void handleUnlockRequested();
}; };
} // namespace ZShell::internal } // namespace ZShell::internal
@@ -5,12 +5,12 @@
namespace { namespace {
constexpr int TOTAL_DURATION_IN_MS = 1800; constexpr int TOTAL_DURATION_IN_MS = 1800;
constexpr std::array DURATION_TO_MOVE_SEGMENT_ENDS = { 533, 567, 850, 750 }; constexpr std::array DURATION_TO_MOVE_SEGMENT_ENDS = {533, 567, 850, 750};
constexpr std::array DELAY_TO_MOVE_SEGMENT_ENDS = { 1267, 1000, 333, 0 }; constexpr std::array DELAY_TO_MOVE_SEGMENT_ENDS = {1267, 1000, 333, 0};
QEasingCurve curve(const QPointF& c1, const QPointF& c2) { QEasingCurve curve(const QPointF& c1, const QPointF& c2) {
QEasingCurve curve(QEasingCurve::BezierSpline); QEasingCurve curve(QEasingCurve::BezierSpline);
curve.addCubicBezierSegment(c1, c2, { 1.0, 1.0 }); curve.addCubicBezierSegment(c1, c2, {1.0, 1.0});
return curve; return curve;
} }
@@ -24,11 +24,7 @@ qreal getFractionInRange(qreal playtime, int start, int duration) {
namespace ZShell::controls { namespace ZShell::controls {
LinearIndicatorSegment::LinearIndicatorSegment(int gap, QObject* parent) LinearIndicatorSegment::LinearIndicatorSegment(int gap, QObject* parent)
: QObject(parent) : QObject(parent), m_startFraction(0), m_endFraction(0), m_gapSize(gap) {}
, m_startFraction(0)
, m_endFraction(0)
, m_gapSize(gap) {
}
qreal LinearIndicatorSegment::startFraction() const { qreal LinearIndicatorSegment::startFraction() const {
return m_startFraction; return m_startFraction;
@@ -45,24 +41,28 @@ int LinearIndicatorSegment::gapSize() const {
LinearIndicatorManager::LinearIndicatorManager(QObject* parent) LinearIndicatorManager::LinearIndicatorManager(QObject* parent)
: QObject(parent) : QObject(parent)
, m_interpolators({ , m_interpolators({
curve({ 0.2, 0.0 }, { 0.8, 1.0 }), curve({0.2, 0.0}, {0.8, 1.0}),
curve({ 0.4, 0.0 }, { 1.0, 1.0 }), curve({0.4, 0.0}, {1.0, 1.0}),
curve({ 0.0, 0.0 }, { 0.65, 1.0 }), curve({0.0, 0.0}, {0.65, 1.0}),
curve({ 0.1, 0.0 }, { 0.45, 1.0 }), curve({0.1, 0.0}, {0.45, 1.0}),
}) })
, m_progress(0) , m_progress(0)
, m_completeEndProgress(0) , m_completeEndProgress(0)
, m_gap(4) , m_gap(4)
, m_activeIndicators({ , m_activeIndicators({
new LinearIndicatorSegment(m_gap, this), new LinearIndicatorSegment(m_gap, this),
new LinearIndicatorSegment(m_gap, this), new LinearIndicatorSegment(m_gap, this),
}) { }) {
for (auto el : m_activeIndicators) for (auto el : m_activeIndicators)
QObject::connect(this, &LinearIndicatorManager::updated, el, &LinearIndicatorSegment::updated); QObject::connect(
this,
&LinearIndicatorManager::updated,
el,
&LinearIndicatorSegment::updated);
} }
QList<LinearIndicatorSegment*> LinearIndicatorManager::activeIndicators() const { QList<LinearIndicatorSegment*> LinearIndicatorManager::activeIndicators() const {
return { m_activeIndicators.cbegin(), m_activeIndicators.cend() }; return {m_activeIndicators.cbegin(), m_activeIndicators.cend()};
} }
qreal LinearIndicatorManager::progress() const { qreal LinearIndicatorManager::progress() const {
@@ -98,12 +98,19 @@ void LinearIndicatorManager::update(qreal progress) {
const auto di = i * 2; const auto di = i * 2;
auto* const indicator = m_activeIndicators[i]; auto* const indicator = m_activeIndicators[i];
auto fraction = getFractionInRange(playtime, DELAY_TO_MOVE_SEGMENT_ENDS[di], DURATION_TO_MOVE_SEGMENT_ENDS[di]); auto fraction = getFractionInRange(
indicator->m_startFraction = std::clamp(m_interpolators[di].valueForProgress(fraction), 0.0, 1.0); playtime,
DELAY_TO_MOVE_SEGMENT_ENDS[di],
DURATION_TO_MOVE_SEGMENT_ENDS[di]);
indicator->m_startFraction =
std::clamp(m_interpolators[di].valueForProgress(fraction), 0.0, 1.0);
fraction = fraction = getFractionInRange(
getFractionInRange(playtime, DELAY_TO_MOVE_SEGMENT_ENDS[di + 1], DURATION_TO_MOVE_SEGMENT_ENDS[di + 1]); playtime,
indicator->m_endFraction = std::clamp(m_interpolators[di + 1].valueForProgress(fraction), 0.0, 1.0); DELAY_TO_MOVE_SEGMENT_ENDS[di + 1],
DURATION_TO_MOVE_SEGMENT_ENDS[di + 1]);
indicator->m_endFraction = std::clamp(
m_interpolators[di + 1].valueForProgress(fraction), 0.0, 1.0);
} }
m_progress = progress; m_progress = progress;
@@ -11,76 +11,80 @@ namespace ZShell::controls {
class LinearIndicatorManager; class LinearIndicatorManager;
class LinearIndicatorSegment : public QObject { class LinearIndicatorSegment : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("LinearIndicatorSegments can only be retrieved from a " QML_UNCREATABLE(
"LinearIndicatorManager.") "LinearIndicatorSegments can only be retrieved from a "
"LinearIndicatorManager.")
Q_PROPERTY(qreal startFraction READ startFraction NOTIFY updated FINAL) Q_PROPERTY(qreal startFraction READ startFraction NOTIFY updated FINAL)
Q_PROPERTY(qreal endFraction READ endFraction NOTIFY updated FINAL) Q_PROPERTY(qreal endFraction READ endFraction NOTIFY updated FINAL)
Q_PROPERTY(int gapSize READ gapSize NOTIFY updated FINAL) Q_PROPERTY(int gapSize READ gapSize NOTIFY updated FINAL)
public: public:
explicit LinearIndicatorSegment(int gap, QObject* parent = nullptr); explicit LinearIndicatorSegment(int gap, QObject* parent = nullptr);
qreal startFraction() const; qreal startFraction() const;
qreal endFraction() const; qreal endFraction() const;
int gapSize() const; int gapSize() const;
signals: signals:
void updated(); void updated();
private: private:
qreal m_startFraction; qreal m_startFraction;
qreal m_endFraction; qreal m_endFraction;
int m_gapSize; int m_gapSize;
friend LinearIndicatorManager; friend LinearIndicatorManager;
}; };
class LinearIndicatorManager : public QObject { class LinearIndicatorManager : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY( Q_PROPERTY(
QList<ZShell::controls::LinearIndicatorSegment*> activeIndicators READ activeIndicators CONSTANT FINAL) QList<ZShell::controls::LinearIndicatorSegment*> activeIndicators READ
activeIndicators CONSTANT FINAL)
Q_PROPERTY(qreal progress READ progress WRITE update NOTIFY updated FINAL) Q_PROPERTY(qreal progress READ progress WRITE update NOTIFY updated FINAL)
Q_PROPERTY(qreal completeEndProgress READ completeEndProgress WRITE updateCompleteEndProgress NOTIFY updated FINAL) Q_PROPERTY(
Q_PROPERTY(int gap READ gap WRITE setGap NOTIFY updated FINAL) qreal completeEndProgress READ completeEndProgress WRITE
updateCompleteEndProgress NOTIFY updated FINAL)
Q_PROPERTY(int gap READ gap WRITE setGap NOTIFY updated FINAL)
Q_PROPERTY(qreal duration READ duration CONSTANT FINAL) Q_PROPERTY(qreal duration READ duration CONSTANT FINAL)
Q_PROPERTY(qreal completeEndDuration READ completeEndDuration CONSTANT FINAL) Q_PROPERTY(qreal completeEndDuration READ completeEndDuration CONSTANT FINAL)
public: public:
explicit LinearIndicatorManager(QObject* parent = nullptr); explicit LinearIndicatorManager(QObject* parent = nullptr);
QList<LinearIndicatorSegment*> activeIndicators() const; QList<LinearIndicatorSegment*> activeIndicators() const;
qreal progress() const; qreal progress() const;
qreal completeEndProgress() const; qreal completeEndProgress() const;
int gap() const; int gap() const;
void setGap(int gap); void setGap(int gap);
int duration() const; int duration() const;
int completeEndDuration() const; int completeEndDuration() const;
void update(qreal progress); void update(qreal progress);
void updateCompleteEndProgress(qreal progress); void updateCompleteEndProgress(qreal progress);
signals: signals:
void updated(); void updated();
private: private:
static constexpr int SEGMENTS = 2; static constexpr int SEGMENTS = 2;
std::array<QEasingCurve, 4> m_interpolators; std::array<QEasingCurve, 4> m_interpolators;
qreal m_progress; qreal m_progress;
qreal m_completeEndProgress; qreal m_completeEndProgress;
int m_gap; int m_gap;
std::array<LinearIndicatorSegment*, SEGMENTS> m_activeIndicators; std::array<LinearIndicatorSegment*, SEGMENTS> m_activeIndicators;
}; };
} // namespace ZShell::controls } // namespace ZShell::controls
+42 -56
View File
@@ -7,47 +7,46 @@
namespace ZShell::internal { namespace ZShell::internal {
SparklineItem::SparklineItem(QQuickItem* parent) SparklineItem::SparklineItem(QQuickItem* parent) : QQuickPaintedItem(parent) {
: QQuickPaintedItem(parent) {
setAntialiasing(true); setAntialiasing(true);
} }
void SparklineItem::paint(QPainter* painter) { void SparklineItem::paint(QPainter* painter) {
const bool has1 = m_line1 && m_line1->count() >= 2; const bool has1 = m_line1 && m_line1->count() >= 2;
const bool has2 = m_line2 && m_line2->count() >= 2; const bool has2 = m_line2 && m_line2->count() >= 2;
if (!has1 && !has2) if (!has1 && !has2) return;
return;
painter->setRenderHint(QPainter::Antialiasing, true); painter->setRenderHint(QPainter::Antialiasing, true);
// Draw line1 first (behind), then line2 (in front) // Draw line1 first (behind), then line2 (in front)
if (has1) if (has1) drawLine(painter, m_line1, m_line1Color, m_line1FillAlpha);
drawLine(painter, m_line1, m_line1Color, m_line1FillAlpha); if (has2) drawLine(painter, m_line2, m_line2Color, m_line2FillAlpha);
if (has2)
drawLine(painter, m_line2, m_line2Color, m_line2FillAlpha);
} }
void SparklineItem::drawLine(QPainter* painter, CircularBuffer* buffer, const QColor& color, qreal fillAlpha) { void SparklineItem::drawLine(
if (m_historyLength < 2) QPainter* painter,
return; CircularBuffer* buffer,
const QColor& color,
qreal fillAlpha) {
if (m_historyLength < 2) return;
const qreal w = width(); const qreal w = width();
const qreal h = height(); const qreal h = height();
const int len = buffer->count(); const int len = buffer->count();
if (len < 2 || m_maxValue <= 0.0) if (len < 2 || m_maxValue <= 0.0) return;
return;
const qreal stepX = w / static_cast<qreal>(m_historyLength - 1); const qreal stepX = w / static_cast<qreal>(m_historyLength - 1);
const qreal startX = w - (len - 1) * stepX - stepX * m_slideProgress + stepX; const qreal startX =
w - (len - 1) * stepX - stepX * m_slideProgress + stepX;
const qreal strokePad = qCeil(m_lineWidth / 2); const qreal strokePad = qCeil(m_lineWidth / 2);
const qreal curvePad = 3.0; const qreal curvePad = 3.0;
const qreal topPad = strokePad + curvePad; const qreal topPad = strokePad + curvePad;
const qreal bottomPad = strokePad; const qreal bottomPad = strokePad;
const qreal plotTop = topPad; const qreal plotTop = topPad;
const qreal plotBottom = h - bottomPad; const qreal plotBottom = h - bottomPad;
const qreal fillBottom = h; const qreal fillBottom = h;
const qreal plotH = qMax<qreal>(1.0, plotBottom - plotTop); const qreal plotH = qMax<qreal>(1.0, plotBottom - plotTop);
QVector<QPointF> points; QVector<QPointF> points;
points.reserve(len); points.reserve(len);
@@ -93,23 +92,22 @@ void SparklineItem::drawLine(QPainter* painter, CircularBuffer* buffer, const QC
} }
void SparklineItem::connectBuffer(CircularBuffer* buffer) { void SparklineItem::connectBuffer(CircularBuffer* buffer) {
if (!buffer) if (!buffer) return;
return;
connect(buffer, &CircularBuffer::valuesChanged, this, [this]() { connect(buffer, &CircularBuffer::valuesChanged, this, [this]() {
update(); update();
}); });
connect(buffer, &QObject::destroyed, this, [this, buffer]() { connect(buffer, &QObject::destroyed, this, [this, buffer]() {
if (m_line1 == buffer) { if (m_line1 == buffer) {
m_line1 = nullptr; m_line1 = nullptr;
emit line1Changed(); emit line1Changed();
} }
if (m_line2 == buffer) { if (m_line2 == buffer) {
m_line2 = nullptr; m_line2 = nullptr;
emit line2Changed(); emit line2Changed();
} }
update(); update();
}); });
} }
CircularBuffer* SparklineItem::line1() const { CircularBuffer* SparklineItem::line1() const {
@@ -117,10 +115,8 @@ CircularBuffer* SparklineItem::line1() const {
} }
void SparklineItem::setLine1(CircularBuffer* buffer) { void SparklineItem::setLine1(CircularBuffer* buffer) {
if (m_line1 == buffer) if (m_line1 == buffer) return;
return; if (m_line1) disconnect(m_line1, nullptr, this, nullptr);
if (m_line1)
disconnect(m_line1, nullptr, this, nullptr);
m_line1 = buffer; m_line1 = buffer;
connectBuffer(buffer); connectBuffer(buffer);
emit line1Changed(); emit line1Changed();
@@ -132,10 +128,8 @@ CircularBuffer* SparklineItem::line2() const {
} }
void SparklineItem::setLine2(CircularBuffer* buffer) { void SparklineItem::setLine2(CircularBuffer* buffer) {
if (m_line2 == buffer) if (m_line2 == buffer) return;
return; if (m_line2) disconnect(m_line2, nullptr, this, nullptr);
if (m_line2)
disconnect(m_line2, nullptr, this, nullptr);
m_line2 = buffer; m_line2 = buffer;
connectBuffer(buffer); connectBuffer(buffer);
emit line2Changed(); emit line2Changed();
@@ -147,8 +141,7 @@ QColor SparklineItem::line1Color() const {
} }
void SparklineItem::setLine1Color(const QColor& color) { void SparklineItem::setLine1Color(const QColor& color) {
if (m_line1Color == color) if (m_line1Color == color) return;
return;
m_line1Color = color; m_line1Color = color;
emit line1ColorChanged(); emit line1ColorChanged();
update(); update();
@@ -159,8 +152,7 @@ QColor SparklineItem::line2Color() const {
} }
void SparklineItem::setLine2Color(const QColor& color) { void SparklineItem::setLine2Color(const QColor& color) {
if (m_line2Color == color) if (m_line2Color == color) return;
return;
m_line2Color = color; m_line2Color = color;
emit line2ColorChanged(); emit line2ColorChanged();
update(); update();
@@ -171,8 +163,7 @@ qreal SparklineItem::line1FillAlpha() const {
} }
void SparklineItem::setLine1FillAlpha(qreal alpha) { void SparklineItem::setLine1FillAlpha(qreal alpha) {
if (qFuzzyCompare(m_line1FillAlpha, alpha)) if (qFuzzyCompare(m_line1FillAlpha, alpha)) return;
return;
m_line1FillAlpha = alpha; m_line1FillAlpha = alpha;
emit line1FillAlphaChanged(); emit line1FillAlphaChanged();
update(); update();
@@ -183,8 +174,7 @@ qreal SparklineItem::line2FillAlpha() const {
} }
void SparklineItem::setLine2FillAlpha(qreal alpha) { void SparklineItem::setLine2FillAlpha(qreal alpha) {
if (qFuzzyCompare(m_line2FillAlpha, alpha)) if (qFuzzyCompare(m_line2FillAlpha, alpha)) return;
return;
m_line2FillAlpha = alpha; m_line2FillAlpha = alpha;
emit line2FillAlphaChanged(); emit line2FillAlphaChanged();
update(); update();
@@ -195,8 +185,7 @@ qreal SparklineItem::maxValue() const {
} }
void SparklineItem::setMaxValue(qreal value) { void SparklineItem::setMaxValue(qreal value) {
if (qFuzzyCompare(m_maxValue, value)) if (qFuzzyCompare(m_maxValue, value)) return;
return;
m_maxValue = value; m_maxValue = value;
emit maxValueChanged(); emit maxValueChanged();
update(); update();
@@ -207,8 +196,7 @@ qreal SparklineItem::slideProgress() const {
} }
void SparklineItem::setSlideProgress(qreal progress) { void SparklineItem::setSlideProgress(qreal progress) {
if (qFuzzyCompare(m_slideProgress, progress)) if (qFuzzyCompare(m_slideProgress, progress)) return;
return;
m_slideProgress = progress; m_slideProgress = progress;
emit slideProgressChanged(); emit slideProgressChanged();
update(); update();
@@ -219,8 +207,7 @@ int SparklineItem::historyLength() const {
} }
void SparklineItem::setHistoryLength(int length) { void SparklineItem::setHistoryLength(int length) {
if (m_historyLength == length) if (m_historyLength == length) return;
return;
m_historyLength = length; m_historyLength = length;
emit historyLengthChanged(); emit historyLengthChanged();
update(); update();
@@ -231,8 +218,7 @@ qreal SparklineItem::lineWidth() const {
} }
void SparklineItem::setLineWidth(qreal width) { void SparklineItem::setLineWidth(qreal width) {
if (qFuzzyCompare(m_lineWidth, width)) if (qFuzzyCompare(m_lineWidth, width)) return;
return;
m_lineWidth = width; m_lineWidth = width;
emit lineWidthChanged(); emit lineWidthChanged();
update(); update();
+80 -59
View File
@@ -10,81 +10,102 @@
namespace ZShell::internal { namespace ZShell::internal {
class SparklineItem : public QQuickPaintedItem { class SparklineItem : public QQuickPaintedItem {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(CircularBuffer* line1 READ line1 WRITE setLine1 NOTIFY line1Changed) Q_PROPERTY(
Q_PROPERTY(CircularBuffer* line2 READ line2 WRITE setLine2 NOTIFY line2Changed) CircularBuffer* line1 READ line1 WRITE setLine1 NOTIFY line1Changed)
Q_PROPERTY(QColor line1Color READ line1Color WRITE setLine1Color NOTIFY line1ColorChanged) Q_PROPERTY(
Q_PROPERTY(QColor line2Color READ line2Color WRITE setLine2Color NOTIFY line2ColorChanged) CircularBuffer* line2 READ line2 WRITE setLine2 NOTIFY line2Changed)
Q_PROPERTY(qreal line1FillAlpha READ line1FillAlpha WRITE setLine1FillAlpha NOTIFY line1FillAlphaChanged) Q_PROPERTY(
Q_PROPERTY(qreal line2FillAlpha READ line2FillAlpha WRITE setLine2FillAlpha NOTIFY line2FillAlphaChanged) QColor line1Color READ line1Color WRITE setLine1Color NOTIFY
Q_PROPERTY(qreal maxValue READ maxValue WRITE setMaxValue NOTIFY maxValueChanged) line1ColorChanged)
Q_PROPERTY(qreal slideProgress READ slideProgress WRITE setSlideProgress NOTIFY slideProgressChanged) Q_PROPERTY(
Q_PROPERTY(int historyLength READ historyLength WRITE setHistoryLength NOTIFY historyLengthChanged) QColor line2Color READ line2Color WRITE setLine2Color NOTIFY
Q_PROPERTY(qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged) line2ColorChanged)
Q_PROPERTY(
qreal line1FillAlpha READ line1FillAlpha WRITE setLine1FillAlpha NOTIFY
line1FillAlphaChanged)
Q_PROPERTY(
qreal line2FillAlpha READ line2FillAlpha WRITE setLine2FillAlpha NOTIFY
line2FillAlphaChanged)
Q_PROPERTY(
qreal maxValue READ maxValue WRITE setMaxValue NOTIFY maxValueChanged)
Q_PROPERTY(
qreal slideProgress READ slideProgress WRITE setSlideProgress NOTIFY
slideProgressChanged)
Q_PROPERTY(
int historyLength READ historyLength WRITE setHistoryLength NOTIFY
historyLengthChanged)
Q_PROPERTY(
qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY
lineWidthChanged)
public: public:
explicit SparklineItem(QQuickItem* parent = nullptr); explicit SparklineItem(QQuickItem* parent = nullptr);
void paint(QPainter* painter) override; void paint(QPainter* painter) override;
[[nodiscard]] CircularBuffer* line1() const; [[nodiscard]] CircularBuffer* line1() const;
void setLine1(CircularBuffer* buffer); void setLine1(CircularBuffer* buffer);
[[nodiscard]] CircularBuffer* line2() const; [[nodiscard]] CircularBuffer* line2() const;
void setLine2(CircularBuffer* buffer); void setLine2(CircularBuffer* buffer);
[[nodiscard]] QColor line1Color() const; [[nodiscard]] QColor line1Color() const;
void setLine1Color(const QColor& color); void setLine1Color(const QColor& color);
[[nodiscard]] QColor line2Color() const; [[nodiscard]] QColor line2Color() const;
void setLine2Color(const QColor& color); void setLine2Color(const QColor& color);
[[nodiscard]] qreal line1FillAlpha() const; [[nodiscard]] qreal line1FillAlpha() const;
void setLine1FillAlpha(qreal alpha); void setLine1FillAlpha(qreal alpha);
[[nodiscard]] qreal line2FillAlpha() const; [[nodiscard]] qreal line2FillAlpha() const;
void setLine2FillAlpha(qreal alpha); void setLine2FillAlpha(qreal alpha);
[[nodiscard]] qreal maxValue() const; [[nodiscard]] qreal maxValue() const;
void setMaxValue(qreal value); void setMaxValue(qreal value);
[[nodiscard]] qreal slideProgress() const; [[nodiscard]] qreal slideProgress() const;
void setSlideProgress(qreal progress); void setSlideProgress(qreal progress);
[[nodiscard]] int historyLength() const; [[nodiscard]] int historyLength() const;
void setHistoryLength(int length); void setHistoryLength(int length);
[[nodiscard]] qreal lineWidth() const; [[nodiscard]] qreal lineWidth() const;
void setLineWidth(qreal width); void setLineWidth(qreal width);
signals: signals:
void line1Changed(); void line1Changed();
void line2Changed(); void line2Changed();
void line1ColorChanged(); void line1ColorChanged();
void line2ColorChanged(); void line2ColorChanged();
void line1FillAlphaChanged(); void line1FillAlphaChanged();
void line2FillAlphaChanged(); void line2FillAlphaChanged();
void maxValueChanged(); void maxValueChanged();
void slideProgressChanged(); void slideProgressChanged();
void historyLengthChanged(); void historyLengthChanged();
void lineWidthChanged(); void lineWidthChanged();
private: private:
void drawLine(QPainter* painter, CircularBuffer* buffer, const QColor& color, qreal fillAlpha); void drawLine(
void connectBuffer(CircularBuffer* buffer); QPainter* painter,
CircularBuffer* buffer,
const QColor& color,
qreal fillAlpha);
void connectBuffer(CircularBuffer* buffer);
CircularBuffer* m_line1 = nullptr; CircularBuffer* m_line1 = nullptr;
CircularBuffer* m_line2 = nullptr; CircularBuffer* m_line2 = nullptr;
QColor m_line1Color; QColor m_line1Color;
QColor m_line2Color; QColor m_line2Color;
qreal m_line1FillAlpha = 0.15; qreal m_line1FillAlpha = 0.15;
qreal m_line2FillAlpha = 0.2; qreal m_line2FillAlpha = 0.2;
qreal m_maxValue = 1024.0; qreal m_maxValue = 1024.0;
qreal m_slideProgress = 0.0; qreal m_slideProgress = 0.0;
int m_historyLength = 30; int m_historyLength = 30;
qreal m_lineWidth = 2.0; qreal m_lineWidth = 2.0;
}; };
} // namespace ZShell::internal } // namespace ZShell::internal
+2 -2
View File
@@ -11,9 +11,9 @@ struct Stroke {
QVector<QPointF> points; QVector<QPointF> points;
QCanvasPath path; QCanvasPath path;
QColor color; QColor color;
float width; qreal width;
int groupId = -1; int groupId = -1;
bool isSinglePoint = false; bool isSinglePoint = false;
}; };
}; }; // namespace ZShell::internal
+38 -46
View File
@@ -6,55 +6,52 @@
namespace ZShell::internal { namespace ZShell::internal {
StrokeCanvasItem::StrokeCanvasItem(QQuickItem *parent) : QCanvasPainterItem(parent) { StrokeCanvasItem::StrokeCanvasItem(QQuickItem* parent)
: QCanvasPainterItem(parent) {
setFillColor(Qt::transparent); setFillColor(Qt::transparent);
setAlphaBlending(true); setAlphaBlending(true);
} }
QCanvasPainterItemRenderer *StrokeCanvasItem::createItemRenderer() const { QCanvasPainterItemRenderer* StrokeCanvasItem::createItemRenderer() const {
return new StrokeCanvasRenderer; return new StrokeCanvasRenderer;
} }
static bool shouldAddPoint( static bool shouldAddPoint(
const QVector<QPointF> &points, const QVector<QPointF>& points, const QPointF& p, qreal minDistance) {
const QPointF &p, if (points.isEmpty()) return true;
qreal minDistance)
{
if (points.isEmpty())
return true;
const QPointF delta = p - points.last(); const QPointF delta = p - points.last();
return QPointF::dotProduct(delta, delta) return QPointF::dotProduct(delta, delta) >= minDistance * minDistance;
>= minDistance * minDistance;
} }
static QCanvasPath buildStrokePath(const QVector<QPointF> &points, float width) { static QCanvasPath buildStrokePath(const QVector<QPointF>& points, qreal width) {
QCanvasPath path; QCanvasPath path;
if (points.size() == 1) { if (points.size() == 1) {
path.circle(points[0], width * 0.5f); path.circle(points[0], width * 0.5);
return path; return path;
} }
auto catmullToBezier = []( auto catmullToBezier = [](const QPointF& p0,
const QPointF &p0, const QPointF &p1, const QPointF& p1,
const QPointF &p2, const QPointF &p3, const QPointF& p2,
float tension, const QPointF& p3,
QPointF &cp1, QPointF &cp2) qreal tension,
{ QPointF& cp1,
cp1 = p1 + (p2 - p0) * tension / 3.0f; QPointF& cp2) {
cp2 = p2 - (p3 - p1) * tension / 3.0f; cp1 = p1 + (p2 - p0) * tension / 3.0;
}; cp2 = p2 - (p3 - p1) * tension / 3.0;
};
const float tension = 0.5f; const qreal tension = 0.5;
path.moveTo(points[0]); path.moveTo(points[0]);
for (int i = 0; i < points.size() - 1; ++i) { for (int i = 0; i < points.size() - 1; ++i) {
const QPointF &p0 = points[qMax(i - 1, 0)]; const QPointF& p0 = points[qMax(i - 1, 0)];
const QPointF &p1 = points[i]; const QPointF& p1 = points[i];
const QPointF &p2 = points[i + 1]; const QPointF& p2 = points[i + 1];
const QPointF &p3 = points[qMin(i + 2, points.size() - 1)]; const QPointF& p3 = points[qMin(i + 2, points.size() - 1)];
QPointF cp1, cp2; QPointF cp1, cp2;
catmullToBezier(p0, p1, p2, p3, tension, cp1, cp2); catmullToBezier(p0, p1, p2, p3, tension, cp1, cp2);
@@ -64,9 +61,8 @@ static QCanvasPath buildStrokePath(const QVector<QPointF> &points, float width)
return path; return path;
} }
void StrokeCanvasItem::setPenColor(const QColor &color) { void StrokeCanvasItem::setPenColor(const QColor& color) {
if (m_penColor == color) if (m_penColor == color) return;
return;
m_penColor = color; m_penColor = color;
update(); update();
@@ -75,8 +71,7 @@ void StrokeCanvasItem::setPenColor(const QColor &color) {
} }
void StrokeCanvasItem::setHoverVisible(bool visible) { void StrokeCanvasItem::setHoverVisible(bool visible) {
if (m_hoverVisible == visible) if (m_hoverVisible == visible) return;
return;
m_hoverVisible = visible; m_hoverVisible = visible;
update(); update();
@@ -84,9 +79,8 @@ void StrokeCanvasItem::setHoverVisible(bool visible) {
emit hoverVisibleChanged(); emit hoverVisibleChanged();
} }
void StrokeCanvasItem::setHoverPoint(const QPointF &point) { void StrokeCanvasItem::setHoverPoint(const QPointF& point) {
if (m_hoverPoint == point) if (m_hoverPoint == point) return;
return;
m_hoverPoint = point; m_hoverPoint = point;
update(); update();
@@ -110,8 +104,7 @@ void StrokeCanvasItem::showHover(qreal x, qreal y) {
} }
void StrokeCanvasItem::hideHover() { void StrokeCanvasItem::hideHover() {
if (!m_hoverVisible) if (!m_hoverVisible) return;
return;
m_hoverVisible = false; m_hoverVisible = false;
update(); update();
@@ -119,9 +112,8 @@ void StrokeCanvasItem::hideHover() {
emit hoverVisibleChanged(); emit hoverVisibleChanged();
} }
void StrokeCanvasItem::setPenWidth(float width) { void StrokeCanvasItem::setPenWidth(qreal width) {
if (qFuzzyCompare(m_penWidth, width)) if (qFuzzyCompare(m_penWidth, width)) return;
return;
m_penWidth = width; m_penWidth = width;
update(); update();
@@ -143,8 +135,7 @@ void StrokeCanvasItem::beginStroke(qreal x, qreal y) {
void StrokeCanvasItem::appendPoint(qreal x, qreal y) { void StrokeCanvasItem::appendPoint(qreal x, qreal y) {
const QPointF incoming{x, y}; const QPointF incoming{x, y};
if (!shouldAddPoint(m_currentStroke.points, incoming, 2.0)) if (!shouldAddPoint(m_currentStroke.points, incoming, 2.0)) return;
return;
QPointF smoothed; QPointF smoothed;
if (m_currentStroke.points.isEmpty()) { if (m_currentStroke.points.isEmpty()) {
@@ -159,7 +150,8 @@ void StrokeCanvasItem::appendPoint(qreal x, qreal y) {
constexpr qreal minAlpha = 0.1; constexpr qreal minAlpha = 0.1;
constexpr qreal maxAlpha = 0.3; constexpr qreal maxAlpha = 0.3;
const qreal t = std::clamp((dist - minDist) / (maxDist - minDist), 0.0, 1.0); const qreal t =
std::clamp((dist - minDist) / (maxDist - minDist), 0.0, 1.0);
const qreal alpha = minAlpha + t * (maxAlpha - minAlpha); const qreal alpha = minAlpha + t * (maxAlpha - minAlpha);
smoothed = last * (1.0 - alpha) + incoming * alpha; smoothed = last * (1.0 - alpha) + incoming * alpha;
@@ -171,11 +163,11 @@ void StrokeCanvasItem::appendPoint(qreal x, qreal y) {
void StrokeCanvasItem::endStroke() { void StrokeCanvasItem::endStroke() {
m_isDrawing = false; m_isDrawing = false;
if (m_currentStroke.points.isEmpty()) if (m_currentStroke.points.isEmpty()) return;
return;
m_currentStroke.isSinglePoint = (m_currentStroke.points.size() == 1); m_currentStroke.isSinglePoint = (m_currentStroke.points.size() == 1);
m_currentStroke.path = buildStrokePath(m_currentStroke.points, m_currentStroke.width); m_currentStroke.path =
buildStrokePath(m_currentStroke.points, m_currentStroke.width);
m_currentStroke.groupId = m_nextGroupId++; m_currentStroke.groupId = m_nextGroupId++;
m_currentStroke.points.clear(); m_currentStroke.points.clear();
m_strokes.append(m_currentStroke); m_strokes.append(m_currentStroke);
@@ -190,4 +182,4 @@ void StrokeCanvasItem::clear() {
update(); update();
} }
}; }; // namespace ZShell::internal
+47 -48
View File
@@ -13,68 +13,67 @@ namespace ZShell::internal {
class StrokeCanvasRenderer; class StrokeCanvasRenderer;
class StrokeCanvasItem : public QCanvasPainterItem { class StrokeCanvasItem : public QCanvasPainterItem {
Q_OBJECT Q_OBJECT
QML_NAMED_ELEMENT(StrokeCanvas) QML_NAMED_ELEMENT(StrokeCanvas)
Q_PROPERTY(QColor penColor READ penColor WRITE setPenColor NOTIFY penColorChanged) Q_PROPERTY(
Q_PROPERTY(bool hoverVisible READ hoverVisible WRITE setHoverVisible NOTIFY hoverVisibleChanged) QColor penColor READ penColor WRITE setPenColor NOTIFY penColorChanged)
Q_PROPERTY(QPointF hoverPoint READ hoverPoint WRITE setHoverPoint NOTIFY hoverPointChanged) Q_PROPERTY(
Q_PROPERTY(qreal penWidth READ penWidth WRITE setPenWidth NOTIFY penWidthChanged) bool hoverVisible READ hoverVisible WRITE setHoverVisible NOTIFY
hoverVisibleChanged)
Q_PROPERTY(
QPointF hoverPoint READ hoverPoint WRITE setHoverPoint NOTIFY
hoverPointChanged)
Q_PROPERTY(
qreal penWidth READ penWidth WRITE setPenWidth NOTIFY penWidthChanged)
public: public:
explicit StrokeCanvasItem(QQuickItem *parent = nullptr); explicit StrokeCanvasItem(QQuickItem* parent = nullptr);
[[nodiscard]] bool hoverVisible() const { [[nodiscard]] bool hoverVisible() const { return m_hoverVisible; }
return m_hoverVisible; [[nodiscard]] QPointF hoverPoint() const { return m_hoverPoint; }
}
[[nodiscard]] QPointF hoverPoint() const {
return m_hoverPoint;
}
void setHoverVisible(bool visible); void setHoverVisible(bool visible);
void setHoverPoint(const QPointF &point); void setHoverPoint(const QPointF& point);
Q_INVOKABLE void showHover(qreal x, qreal y); Q_INVOKABLE void showHover(qreal x, qreal y);
Q_INVOKABLE void hideHover(); Q_INVOKABLE void hideHover();
[[nodiscard]] QColor penColor() const { [[nodiscard]] QColor penColor() const { return m_penColor; }
return m_penColor; [[nodiscard]] qreal penWidth() const { return m_penWidth; }
}
[[nodiscard]] float penWidth() const {
return m_penWidth;
}
void setPenColor(const QColor &color); void setPenColor(const QColor& color);
void setPenWidth(float width); void setPenWidth(qreal width);
Q_INVOKABLE void clear(); Q_INVOKABLE void clear();
Q_INVOKABLE void beginStroke(qreal x, qreal y); Q_INVOKABLE void beginStroke(qreal x, qreal y);
Q_INVOKABLE void appendPoint(qreal x, qreal y); Q_INVOKABLE void appendPoint(qreal x, qreal y);
Q_INVOKABLE void endStroke(); Q_INVOKABLE void endStroke();
signals: signals:
void penColorChanged(); void penColorChanged();
void penWidthChanged(); void penWidthChanged();
void hoverVisibleChanged(); void hoverVisibleChanged();
void hoverPointChanged(); void hoverPointChanged();
protected: protected:
[[nodiscard]] QCanvasPainterItemRenderer *createItemRenderer() const override; [[nodiscard]] QCanvasPainterItemRenderer* createItemRenderer()
const override;
private: private:
friend class StrokeCanvasRenderer; friend class StrokeCanvasRenderer;
bool m_hoverVisible = false; bool m_hoverVisible = false;
QPointF m_hoverPoint; QPointF m_hoverPoint;
QColor m_penColor = Qt::white; QColor m_penColor = Qt::white;
float m_penWidth = 4.f; qreal m_penWidth = 4.0;
bool m_isDrawing = false; bool m_isDrawing = false;
int m_nextGroupId = 0; int m_nextGroupId = 0;
QVector<Stroke> m_strokes; QVector<Stroke> m_strokes;
Stroke m_currentStroke; Stroke m_currentStroke;
}; };
}; }; // namespace ZShell::internal
@@ -5,13 +5,11 @@
namespace ZShell::internal { namespace ZShell::internal {
static void drawStroke( static void drawStroke(
QCanvasPainter *painter, QCanvasPainter* painter,
const QVector<QPointF> &points, const QVector<QPointF>& points,
const QColor &color, const QColor& color,
float width) { qreal width) {
if (points.isEmpty()) return;
if (points.isEmpty())
return;
painter->setStrokeStyle(color); painter->setStrokeStyle(color);
painter->setFillStyle(color); painter->setFillStyle(color);
@@ -21,31 +19,32 @@ static void drawStroke(
if (points.size() == 1) { if (points.size() == 1) {
painter->beginPath(); painter->beginPath();
painter->circle(points.front(), width * 0.5f); painter->circle(points.front(), width * 0.5);
painter->fill(); painter->fill();
return; return;
} }
auto catmullToBezier = []( auto catmullToBezier = [](const QPointF& p0,
const QPointF &p0, const QPointF &p1, const QPointF& p1,
const QPointF &p2, const QPointF &p3, const QPointF& p2,
float tension, const QPointF& p3,
QPointF &cp1, QPointF &cp2) qreal tension,
{ QPointF& cp1,
cp1 = p1 + (p2 - p0) * tension / 3.0f; QPointF& cp2) {
cp2 = p2 - (p3 - p1) * tension / 3.0f; cp1 = p1 + (p2 - p0) * tension / 3.0;
}; cp2 = p2 - (p3 - p1) * tension / 3.0;
};
const float tension = 0.5f; // increase toward 1.0 for tighter curves const qreal tension = 0.5; // increase toward 1.0 for tighter curves
painter->beginPath(); painter->beginPath();
painter->moveTo(points[0]); painter->moveTo(points[0]);
for (int i = 0; i < points.size() - 1; ++i) { for (int i = 0; i < points.size() - 1; ++i) {
const QPointF &p0 = points[qMax(i - 1, 0)]; const QPointF& p0 = points[qMax(i - 1, 0)];
const QPointF &p1 = points[i]; const QPointF& p1 = points[i];
const QPointF &p2 = points[i + 1]; const QPointF& p2 = points[i + 1];
const QPointF &p3 = points[qMin(i + 2, points.size() - 1)]; const QPointF& p3 = points[qMin(i + 2, points.size() - 1)];
QPointF cp1, cp2; QPointF cp1, cp2;
catmullToBezier(p0, p1, p2, p3, tension, cp1, cp2); catmullToBezier(p0, p1, p2, p3, tension, cp1, cp2);
@@ -56,13 +55,12 @@ static void drawStroke(
} }
static void drawHoverCursor( static void drawHoverCursor(
QCanvasPainter *painter, QCanvasPainter* painter,
const QPointF &point, const QPointF& point,
float penWidth, qreal penWidth,
QColor penColor, QColor penColor,
bool isDrawing) bool isDrawing) {
{ const qreal radius = penWidth * 0.5;
const float radius = penWidth * 0.5f;
if (isDrawing) { if (isDrawing) {
painter->setFillStyle(penColor); painter->setFillStyle(penColor);
@@ -71,67 +69,71 @@ static void drawHoverCursor(
painter->fill(); painter->fill();
} }
const float lineWidth = 1.5f; const qreal lineWidth = 1.5;
const float crosshairSize = 6.0f; const qreal crosshairSize = 6.0;
const bool useDashes = penWidth > 10.0f; const bool useDashes = penWidth > 10.0;
auto drawOutline = [&](const QColor &color, float width) { auto drawOutline = [&](const QColor& color, qreal width) {
painter->setStrokeStyle(color); painter->setStrokeStyle(color);
painter->setLineWidth(width); painter->setLineWidth(width);
painter->setLineCap(QCanvasPainter::LineCap::Round); painter->setLineCap(QCanvasPainter::LineCap::Round);
if (useDashes) { if (useDashes) {
const int dashCount = 12; const int dashCount = 12;
const float fullAngle = 2.0f * M_PI; const qreal fullAngle = 2.0 * M_PI;
const float dashAngle = fullAngle / dashCount * 0.5f; const qreal dashAngle = fullAngle / dashCount * 0.5;
const float gapAngle = fullAngle / dashCount * 0.5f; const qreal gapAngle = fullAngle / dashCount * 0.5;
float angle = 0.0f; qreal angle = 0.0;
for (int i = 0; i < dashCount; ++i) { for (int i = 0; i < dashCount; ++i) {
painter->beginPath(); painter->beginPath();
painter->arc(point, radius, angle, angle + dashAngle, painter->arc(
QCanvasPainter::PathWinding::ClockWise, point,
QCanvasPainter::PathConnection::NotConnected); radius,
painter->stroke(); angle,
angle += dashAngle + gapAngle; angle + dashAngle,
} QCanvasPainter::PathWinding::ClockWise,
} else { QCanvasPainter::PathConnection::NotConnected);
painter->beginPath(); painter->stroke();
painter->circle(point, radius); angle += dashAngle + gapAngle;
painter->stroke(); }
} } else {
}; painter->beginPath();
painter->circle(point, radius);
painter->stroke();
}
};
auto drawCrosshair = [&](const QColor &color, float width) { auto drawCrosshair = [&](const QColor& color, qreal width) {
painter->setStrokeStyle(color); painter->setStrokeStyle(color);
painter->setLineWidth(width); painter->setLineWidth(width);
painter->setLineCap(QCanvasPainter::LineCap::Round); painter->setLineCap(QCanvasPainter::LineCap::Round);
const float inner = radius + 3.0f; const qreal inner = radius + 3.0;
const float outer = radius + 3.0f + crosshairSize; const qreal outer = radius + 3.0 + crosshairSize;
painter->beginPath(); painter->beginPath();
painter->moveTo(point + QPointF(0, -outer)); painter->moveTo(point + QPointF(0, -outer));
painter->lineTo(point + QPointF(0, -inner)); painter->lineTo(point + QPointF(0, -inner));
painter->moveTo(point + QPointF(0, outer)); painter->moveTo(point + QPointF(0, outer));
painter->lineTo(point + QPointF(0, inner)); painter->lineTo(point + QPointF(0, inner));
painter->moveTo(point + QPointF(-outer, 0)); painter->moveTo(point + QPointF(-outer, 0));
painter->lineTo(point + QPointF(-inner, 0)); painter->lineTo(point + QPointF(-inner, 0));
painter->moveTo(point + QPointF( outer, 0)); painter->moveTo(point + QPointF(outer, 0));
painter->lineTo(point + QPointF( inner, 0)); painter->lineTo(point + QPointF(inner, 0));
painter->stroke(); painter->stroke();
}; };
drawOutline(QColor(0, 0, 0, 160), lineWidth + 1.0f); drawOutline(QColor(0, 0, 0, 160), lineWidth + 1.0);
drawCrosshair(QColor(0, 0, 0, 160), lineWidth + 1.0f); drawCrosshair(QColor(0, 0, 0, 160), lineWidth + 1.0);
drawOutline(QColor(255, 255, 255, 220), lineWidth); drawOutline(QColor(255, 255, 255, 220), lineWidth);
drawCrosshair(QColor(255, 255, 255, 220), lineWidth); drawCrosshair(QColor(255, 255, 255, 220), lineWidth);
} }
void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem *item) { void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem* item) {
auto *canvas = static_cast<StrokeCanvasItem *>(item); auto* canvas = static_cast<StrokeCanvasItem*>(item);
m_penColor = canvas->m_penColor; m_penColor = canvas->m_penColor;
m_penWidth = canvas->m_penWidth; m_penWidth = canvas->m_penWidth;
@@ -140,7 +142,7 @@ void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem *item) {
m_strokes.append(canvas->m_strokes[m_strokes.size()]); m_strokes.append(canvas->m_strokes[m_strokes.size()]);
if (canvas->m_strokes.isEmpty() && !m_strokes.isEmpty()) { if (canvas->m_strokes.isEmpty() && !m_strokes.isEmpty()) {
for (const auto &stroke : m_strokes) for (const auto& stroke : m_strokes)
if (stroke.groupId >= 0) if (stroke.groupId >= 0)
m_pendingGroupRemovals.append(stroke.groupId); m_pendingGroupRemovals.append(stroke.groupId);
m_strokes.clear(); m_strokes.clear();
@@ -153,14 +155,14 @@ void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem *item) {
m_isDrawing = canvas->m_isDrawing; m_isDrawing = canvas->m_isDrawing;
} }
void StrokeCanvasRenderer::paint(QCanvasPainter *painter) { void StrokeCanvasRenderer::paint(QCanvasPainter* painter) {
for (int id : m_pendingGroupRemovals) for (int id : m_pendingGroupRemovals)
painter->removePathGroup(id); painter->removePathGroup(id);
m_pendingGroupRemovals.clear(); m_pendingGroupRemovals.clear();
painter->clearRect(0, 0, width(), height()); painter->clearRect(0, 0, width(), height());
for (const auto &stroke : m_strokes) { for (const auto& stroke : m_strokes) {
painter->setStrokeStyle(stroke.color); painter->setStrokeStyle(stroke.color);
painter->setFillStyle(stroke.color); painter->setFillStyle(stroke.color);
painter->setLineWidth(stroke.width); painter->setLineWidth(stroke.width);
@@ -174,10 +176,15 @@ void StrokeCanvasRenderer::paint(QCanvasPainter *painter) {
} }
} }
drawStroke(painter, m_currentStroke.points, m_currentStroke.color, m_currentStroke.width); drawStroke(
painter,
m_currentStroke.points,
m_currentStroke.color,
m_currentStroke.width);
if (m_hoverVisible) if (m_hoverVisible)
drawHoverCursor(painter, m_hoverPoint, m_penWidth, m_penColor, m_isDrawing); drawHoverCursor(
painter, m_hoverPoint, m_penWidth, m_penColor, m_isDrawing);
} }
}; }; // namespace ZShell::internal
@@ -7,22 +7,20 @@
namespace ZShell::internal { namespace ZShell::internal {
class StrokeCanvasRenderer final : public QCanvasPainterItemRenderer { class StrokeCanvasRenderer final : public QCanvasPainterItemRenderer {
public:
void synchronizeData(QCanvasPainterItem* item) override;
void paint(QCanvasPainter* painter) override;
public: private:
void synchronizeData(QCanvasPainterItem *item) override; QColor m_penColor;
void paint(QCanvasPainter *painter) override; qreal m_penWidth = 4.0;
bool m_hoverVisible = false;
private: QPointF m_hoverPoint;
QColor m_penColor; bool m_isDrawing = false;
float m_penWidth = 4.f;
bool m_hoverVisible = false;
QPointF m_hoverPoint;
bool m_isDrawing = false;
QVector<Stroke> m_strokes;
Stroke m_currentStroke;
QVector<int> m_pendingGroupRemovals;
QVector<Stroke> m_strokes;
Stroke m_currentStroke;
QVector<int> m_pendingGroupRemovals;
}; };
}; }; // namespace ZShell::internal
+12 -23
View File
@@ -9,14 +9,12 @@
namespace ZShell::internal { namespace ZShell::internal {
VisualizerBars::VisualizerBars(QQuickItem* parent) VisualizerBars::VisualizerBars(QQuickItem* parent) : QQuickPaintedItem(parent) {
: QQuickPaintedItem(parent) {
setAntialiasing(true); setAntialiasing(true);
} }
void VisualizerBars::advance(qreal dt) { void VisualizerBars::advance(qreal dt) {
if (m_displayValues.isEmpty() || m_settled) if (m_displayValues.isEmpty() || m_settled) return;
return;
// dt is in seconds (from FrameAnimation.frameTime), convert to ms // dt is in seconds (from FrameAnimation.frameTime), convert to ms
const qreal dtMs = dt * 1000.0; const qreal dtMs = dt * 1000.0;
@@ -45,8 +43,7 @@ void VisualizerBars::advance(qreal dt) {
} }
void VisualizerBars::paint(QPainter* painter) { void VisualizerBars::paint(QPainter* painter) {
if (m_displayValues.isEmpty()) if (m_displayValues.isEmpty()) return;
return;
painter->setRenderHint(QPainter::Antialiasing, true); painter->setRenderHint(QPainter::Antialiasing, true);
painter->setPen(Qt::NoPen); painter->setPen(Qt::NoPen);
@@ -68,15 +65,13 @@ void VisualizerBars::drawSide(QPainter* painter, bool rightSide) {
const qreal h = height(); const qreal h = height();
const auto count = m_displayValues.size(); const auto count = m_displayValues.size();
if (count == 0) if (count == 0) return;
return;
const qreal sideWidth = w * 0.4; const qreal sideWidth = w * 0.4;
const qreal slotWidth = sideWidth / static_cast<qreal>(count); const qreal slotWidth = sideWidth / static_cast<qreal>(count);
const qreal barWidth = slotWidth - m_spacing; const qreal barWidth = slotWidth - m_spacing;
if (barWidth <= 0) if (barWidth <= 0) return;
return;
const qreal sideOffset = rightSide ? w * 0.6 : 0; const qreal sideOffset = rightSide ? w * 0.6 : 0;
const qreal maxBarHeight = h * 0.4; const qreal maxBarHeight = h * 0.4;
@@ -86,12 +81,11 @@ void VisualizerBars::drawSide(QPainter* painter, bool rightSide) {
const qreal value = std::clamp(m_displayValues[valueIndex], 0.0, 1.0); const qreal value = std::clamp(m_displayValues[valueIndex], 0.0, 1.0);
const qreal barHeight = value * maxBarHeight; const qreal barHeight = value * maxBarHeight;
if (barHeight <= 0) if (barHeight <= 0) continue;
continue;
const qreal x = static_cast<qreal>(i) * slotWidth + sideOffset; const qreal x = static_cast<qreal>(i) * slotWidth + sideOffset;
const qreal y = h - barHeight; const qreal y = h - barHeight;
const qreal r = std::min({ m_rounding, barWidth / 2.0, barHeight }); const qreal r = std::min({m_rounding, barWidth / 2.0, barHeight});
QPainterPath path; QPainterPath path;
path.moveTo(x, h); path.moveTo(x, h);
@@ -141,8 +135,7 @@ QColor VisualizerBars::primaryColor() const {
} }
void VisualizerBars::setPrimaryColor(const QColor& color) { void VisualizerBars::setPrimaryColor(const QColor& color) {
if (m_primaryColor == color) if (m_primaryColor == color) return;
return;
m_primaryColor = color; m_primaryColor = color;
emit primaryColorChanged(); emit primaryColorChanged();
update(); update();
@@ -153,8 +146,7 @@ QColor VisualizerBars::secondaryColor() const {
} }
void VisualizerBars::setSecondaryColor(const QColor& color) { void VisualizerBars::setSecondaryColor(const QColor& color) {
if (m_secondaryColor == color) if (m_secondaryColor == color) return;
return;
m_secondaryColor = color; m_secondaryColor = color;
emit secondaryColorChanged(); emit secondaryColorChanged();
update(); update();
@@ -165,8 +157,7 @@ qreal VisualizerBars::rounding() const {
} }
void VisualizerBars::setRounding(qreal rounding) { void VisualizerBars::setRounding(qreal rounding) {
if (qFuzzyCompare(m_rounding, rounding)) if (qFuzzyCompare(m_rounding, rounding)) return;
return;
m_rounding = rounding; m_rounding = rounding;
emit roundingChanged(); emit roundingChanged();
update(); update();
@@ -177,8 +168,7 @@ qreal VisualizerBars::spacing() const {
} }
void VisualizerBars::setSpacing(qreal spacing) { void VisualizerBars::setSpacing(qreal spacing) {
if (qFuzzyCompare(m_spacing, spacing)) if (qFuzzyCompare(m_spacing, spacing)) return;
return;
m_spacing = spacing; m_spacing = spacing;
emit spacingChanged(); emit spacingChanged();
update(); update();
@@ -189,8 +179,7 @@ int VisualizerBars::animationDuration() const {
} }
void VisualizerBars::setAnimationDuration(int duration) { void VisualizerBars::setAnimationDuration(int duration) {
if (m_animationDuration == duration) if (m_animationDuration == duration) return;
return;
m_animationDuration = duration; m_animationDuration = duration;
emit animationDurationChanged(); emit animationDurationChanged();
} }
+52 -44
View File
@@ -9,64 +9,72 @@
namespace ZShell::internal { namespace ZShell::internal {
class VisualizerBars : public QQuickPaintedItem { class VisualizerBars : public QQuickPaintedItem {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(QVector<double> values READ values WRITE setValues NOTIFY valuesChanged) Q_PROPERTY(
Q_PROPERTY(QColor primaryColor READ primaryColor WRITE setPrimaryColor NOTIFY primaryColorChanged) QVector<double> values READ values WRITE setValues NOTIFY valuesChanged)
Q_PROPERTY(QColor secondaryColor READ secondaryColor WRITE setSecondaryColor NOTIFY secondaryColorChanged) Q_PROPERTY(
Q_PROPERTY(qreal rounding READ rounding WRITE setRounding NOTIFY roundingChanged) QColor primaryColor READ primaryColor WRITE setPrimaryColor NOTIFY
Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged) primaryColorChanged)
Q_PROPERTY(int animationDuration READ animationDuration WRITE setAnimationDuration NOTIFY animationDurationChanged) Q_PROPERTY(
Q_PROPERTY(bool settled READ settled NOTIFY settledChanged) QColor secondaryColor READ secondaryColor WRITE setSecondaryColor NOTIFY
secondaryColorChanged)
Q_PROPERTY(
qreal rounding READ rounding WRITE setRounding NOTIFY roundingChanged)
Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
Q_PROPERTY(
int animationDuration READ animationDuration WRITE setAnimationDuration
NOTIFY animationDurationChanged)
Q_PROPERTY(bool settled READ settled NOTIFY settledChanged)
public: public:
explicit VisualizerBars(QQuickItem* parent = nullptr); explicit VisualizerBars(QQuickItem* parent = nullptr);
void paint(QPainter* painter) override; void paint(QPainter* painter) override;
Q_INVOKABLE void advance(qreal dt); Q_INVOKABLE void advance(qreal dt);
[[nodiscard]] QVector<double> values() const; [[nodiscard]] QVector<double> values() const;
void setValues(const QVector<double>& values); void setValues(const QVector<double>& values);
[[nodiscard]] QColor primaryColor() const; [[nodiscard]] QColor primaryColor() const;
void setPrimaryColor(const QColor& color); void setPrimaryColor(const QColor& color);
[[nodiscard]] QColor secondaryColor() const; [[nodiscard]] QColor secondaryColor() const;
void setSecondaryColor(const QColor& color); void setSecondaryColor(const QColor& color);
[[nodiscard]] qreal rounding() const; [[nodiscard]] qreal rounding() const;
void setRounding(qreal rounding); void setRounding(qreal rounding);
[[nodiscard]] qreal spacing() const; [[nodiscard]] qreal spacing() const;
void setSpacing(qreal spacing); void setSpacing(qreal spacing);
[[nodiscard]] int animationDuration() const; [[nodiscard]] int animationDuration() const;
void setAnimationDuration(int duration); void setAnimationDuration(int duration);
[[nodiscard]] bool settled() const; [[nodiscard]] bool settled() const;
signals: signals:
void valuesChanged(); void valuesChanged();
void primaryColorChanged(); void primaryColorChanged();
void secondaryColorChanged(); void secondaryColorChanged();
void roundingChanged(); void roundingChanged();
void spacingChanged(); void spacingChanged();
void animationDurationChanged(); void animationDurationChanged();
void settledChanged(); void settledChanged();
private: private:
void drawSide(QPainter* painter, bool rightSide); void drawSide(QPainter* painter, bool rightSide);
QVector<double> m_targetValues; QVector<double> m_targetValues;
QVector<double> m_displayValues; QVector<double> m_displayValues;
QColor m_primaryColor; QColor m_primaryColor;
QColor m_secondaryColor; QColor m_secondaryColor;
qreal m_rounding = 0.0; qreal m_rounding = 0.0;
qreal m_spacing = 0.0; qreal m_spacing = 0.0;
int m_animationDuration = 200; int m_animationDuration = 200;
bool m_settled = true; bool m_settled = true;
}; };
} // namespace ZShell::internal } // namespace ZShell::internal
+43 -32
View File
@@ -7,15 +7,16 @@
#include <QtConcurrent> #include <QtConcurrent>
#include <QSGImageNode> #include <QSGImageNode>
#include <QQuickWindow> #include <QQuickWindow>
#include <set>
namespace ZShell::internal { namespace ZShell::internal {
WallpaperImage::WallpaperImage(QQuickItem *parent) WallpaperImage::WallpaperImage(QQuickItem* parent) : QQuickItem(parent) {
: QQuickItem(parent)
{
setFlag(ItemHasContents, true); setFlag(ItemHasContents, true);
connect(&m_imageWatcher, &QFutureWatcher<QImage>::finished, this, &WallpaperImage::handleImageLoaded); connect(
&m_imageWatcher,
&QFutureWatcher<QImage>::finished,
this,
&WallpaperImage::handleImageLoaded);
} }
WallpaperImage::~WallpaperImage() { WallpaperImage::~WallpaperImage() {
@@ -23,14 +24,13 @@ WallpaperImage::~WallpaperImage() {
} }
void WallpaperImage::setStatus(const Status s) { void WallpaperImage::setStatus(const Status s) {
if (m_status == s) if (m_status == s) return;
return;
m_status = s; m_status = s;
emit statusChanged(); emit statusChanged();
} }
void WallpaperImage::setSource(const QUrl &source) { void WallpaperImage::setSource(const QUrl& source) {
if (m_source == source) return; if (m_source == source) return;
m_source = source; m_source = source;
emit sourceChanged(); emit sourceChanged();
@@ -39,7 +39,7 @@ void WallpaperImage::setSource(const QUrl &source) {
loadImage(); loadImage();
} }
void WallpaperImage::setScreenResolution(const QSize &screenResolution) { void WallpaperImage::setScreenResolution(const QSize& screenResolution) {
if (m_screenResolution == screenResolution) return; if (m_screenResolution == screenResolution) return;
m_screenResolution = screenResolution; m_screenResolution = screenResolution;
emit screenResolutionChanged(); emit screenResolutionChanged();
@@ -86,11 +86,16 @@ void WallpaperImage::setCropHeight(qreal h) {
QString WallpaperImage::getCacheFilePath() const { QString WallpaperImage::getCacheFilePath() const {
if (m_source.isEmpty() || m_screenResolution.isEmpty()) return QString(); if (m_source.isEmpty() || m_screenResolution.isEmpty()) return QString();
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/zshell/imagecache"; QString cachePath =
QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) +
"/zshell/imagecache";
QDir().mkpath(cachePath); QDir().mkpath(cachePath);
QString id = m_source.toString() + "_" + QString::number(m_screenResolution.width()) + "x" + QString::number(m_screenResolution.height()); QString id = m_source.toString() + "_" +
QByteArray hash = QCryptographicHash::hash(id.toUtf8(), QCryptographicHash::Md5).toHex(); QString::number(m_screenResolution.width()) + "x" +
QString::number(m_screenResolution.height());
QByteArray hash =
QCryptographicHash::hash(id.toUtf8(), QCryptographicHash::Md5).toHex();
return cachePath + "/" + hash + ".png"; return cachePath + "/" + hash + ".png";
} }
@@ -102,7 +107,8 @@ void WallpaperImage::loadImage() {
} }
QString cacheFile = getCacheFilePath(); QString cacheFile = getCacheFilePath();
QString sourceFile = m_source.isLocalFile() ? m_source.toLocalFile() : m_source.toString(); QString sourceFile = m_source.isLocalFile() ? m_source.toLocalFile()
: m_source.toString();
if (sourceFile.startsWith("qrc:/")) { if (sourceFile.startsWith("qrc:/")) {
sourceFile = sourceFile.mid(3); sourceFile = sourceFile.mid(3);
@@ -110,8 +116,10 @@ void WallpaperImage::loadImage() {
QSize targetRes = m_screenResolution; QSize targetRes = m_screenResolution;
QFuture<QImage> future = QtConcurrent::run([sourceFile, cacheFile, targetRes]() -> QImage { QFuture<QImage> future =
if (!targetRes.isEmpty() && !cacheFile.isEmpty() && QFileInfo::exists(cacheFile)) { QtConcurrent::run([sourceFile, cacheFile, targetRes]() -> QImage {
if (!targetRes.isEmpty() && !cacheFile.isEmpty() &&
QFileInfo::exists(cacheFile)) {
QImage cached(cacheFile); QImage cached(cacheFile);
if (!cached.isNull()) return cached; if (!cached.isNull()) return cached;
} }
@@ -123,8 +131,12 @@ void WallpaperImage::loadImage() {
return original; return original;
} }
if (original.width() > targetRes.width() || original.height() > targetRes.height()) { if (original.width() > targetRes.width() ||
QImage scaled = original.scaled(targetRes, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); original.height() > targetRes.height()) {
QImage scaled = original.scaled(
targetRes,
Qt::KeepAspectRatioByExpanding,
Qt::SmoothTransformation);
if (!cacheFile.isEmpty()) scaled.save(cacheFile, "PNG"); if (!cacheFile.isEmpty()) scaled.save(cacheFile, "PNG");
return scaled; return scaled;
} }
@@ -145,8 +157,9 @@ void WallpaperImage::handleImageLoaded() {
update(); update();
} }
QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { QSGNode* WallpaperImage::updatePaintNode(
auto *node = static_cast<QSGImageNode *>(oldNode); QSGNode* oldNode, UpdatePaintNodeData*) {
auto* node = static_cast<QSGImageNode*>(oldNode);
if (m_image.isNull()) { if (m_image.isNull()) {
delete node; delete node;
@@ -159,7 +172,8 @@ QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
if (m_textureDirty) { if (m_textureDirty) {
if (m_texture) delete m_texture; if (m_texture) delete m_texture;
m_texture = window()->createTextureFromImage(m_image, QQuickWindow::TextureHasAlphaChannel); m_texture = window()->createTextureFromImage(
m_image, QQuickWindow::TextureHasAlphaChannel);
m_textureDirty = false; m_textureDirty = false;
} }
@@ -175,8 +189,7 @@ QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
m_cropX * m_texture->textureSize().width(), m_cropX * m_texture->textureSize().width(),
m_cropY * m_texture->textureSize().height(), m_cropY * m_texture->textureSize().height(),
cW * m_texture->textureSize().width(), cW * m_texture->textureSize().width(),
cH * m_texture->textureSize().height() cH * m_texture->textureSize().height());
);
QRectF bounds = boundingRect(); QRectF bounds = boundingRect();
if (bounds.isEmpty() || reqRect.isEmpty()) return node; if (bounds.isEmpty() || reqRect.isEmpty()) return node;
@@ -202,25 +215,23 @@ QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
sourceRect.x() / m_texture->textureSize().width(), sourceRect.x() / m_texture->textureSize().width(),
sourceRect.y() / m_texture->textureSize().height(), sourceRect.y() / m_texture->textureSize().height(),
sourceRect.width() / m_texture->textureSize().width(), sourceRect.width() / m_texture->textureSize().width(),
sourceRect.height() / m_texture->textureSize().height() sourceRect.height() / m_texture->textureSize().height());
);
bool changed = false; bool changed = false;
auto updateIfChanged = [&](qreal &dst, qreal value) { auto updateIfChanged = [&](qreal& dst, qreal value) {
if (!qFuzzyCompare(dst, value)) { if (!qFuzzyCompare(dst, value)) {
dst = value; dst = value;
changed = true; changed = true;
} }
}; };
updateIfChanged(m_actualCropX, normalizedActual.x()); updateIfChanged(m_actualCropX, normalizedActual.x());
updateIfChanged(m_actualCropY, normalizedActual.y()); updateIfChanged(m_actualCropY, normalizedActual.y());
updateIfChanged(m_actualCropWidth, normalizedActual.width()); updateIfChanged(m_actualCropWidth, normalizedActual.width());
updateIfChanged(m_actualCropHeight, normalizedActual.height()); updateIfChanged(m_actualCropHeight, normalizedActual.height());
if (changed) if (changed) emit actualCropChanged();
emit actualCropChanged();
node->setSourceRect(sourceRect); node->setSourceRect(sourceRect);
} }
+80 -100
View File
@@ -11,129 +11,109 @@
namespace ZShell::internal { namespace ZShell::internal {
class WallpaperImage : public QQuickItem { class WallpaperImage : public QQuickItem {
Q_OBJECT Q_OBJECT
QML_NAMED_ELEMENT(WallpaperImage) QML_NAMED_ELEMENT(WallpaperImage)
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(QSize screenResolution READ screenResolution WRITE setScreenResolution NOTIFY screenResolutionChanged) Q_PROPERTY(
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged) QSize screenResolution READ screenResolution WRITE setScreenResolution
Q_PROPERTY(Status status READ status NOTIFY statusChanged) NOTIFY screenResolutionChanged)
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged)
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
Q_PROPERTY(qreal actualCropX READ actualCropX NOTIFY actualCropChanged) Q_PROPERTY(qreal actualCropX READ actualCropX NOTIFY actualCropChanged)
Q_PROPERTY(qreal actualCropY READ actualCropY NOTIFY actualCropChanged) Q_PROPERTY(qreal actualCropY READ actualCropY NOTIFY actualCropChanged)
Q_PROPERTY(qreal actualCropWidth READ actualCropWidth NOTIFY actualCropChanged) Q_PROPERTY(
Q_PROPERTY(qreal actualCropHeight READ actualCropHeight NOTIFY actualCropChanged) qreal actualCropWidth READ actualCropWidth NOTIFY actualCropChanged)
Q_PROPERTY(
qreal actualCropHeight READ actualCropHeight NOTIFY actualCropChanged)
Q_PROPERTY(qreal cropX READ cropX WRITE setCropX NOTIFY cropXChanged) Q_PROPERTY(qreal cropX READ cropX WRITE setCropX NOTIFY cropXChanged)
Q_PROPERTY(qreal cropY READ cropY WRITE setCropY NOTIFY cropYChanged) Q_PROPERTY(qreal cropY READ cropY WRITE setCropY NOTIFY cropYChanged)
Q_PROPERTY(qreal cropWidth READ cropWidth WRITE setCropWidth NOTIFY cropWidthChanged) Q_PROPERTY(
Q_PROPERTY(qreal cropHeight READ cropHeight WRITE setCropHeight NOTIFY cropHeightChanged) qreal cropWidth READ cropWidth WRITE setCropWidth NOTIFY
cropWidthChanged)
Q_PROPERTY(
qreal cropHeight READ cropHeight WRITE setCropHeight NOTIFY
cropHeightChanged)
public: public:
explicit WallpaperImage(QQuickItem *parent = nullptr); explicit WallpaperImage(QQuickItem* parent = nullptr);
~WallpaperImage() override; ~WallpaperImage() override;
enum Status { enum Status { Null, Ready, Loading, Error };
Null, Q_ENUM(Status)
Ready,
Loading,
Error
};
Q_ENUM(Status)
Status status() const { Status status() const { return m_status; }
return m_status;
}
QUrl source() const { QUrl source() const { return m_source; }
return m_source; void setSource(const QUrl& source);
}
void setSource(const QUrl &source);
QSize screenResolution() const { QSize screenResolution() const { return m_screenResolution; }
return m_screenResolution; void setScreenResolution(const QSize& screenResolution);
}
void setScreenResolution(const QSize &screenResolution);
qreal zoom() const { qreal zoom() const { return m_zoom; }
return m_zoom; void setZoom(qreal zoom);
}
void setZoom(qreal zoom);
qreal actualCropX() const { qreal actualCropX() const { return m_actualCropX; }
return m_actualCropX;
}
qreal actualCropY() const { qreal actualCropY() const { return m_actualCropY; }
return m_actualCropY;
}
qreal actualCropWidth() const { qreal actualCropWidth() const { return m_actualCropWidth; }
return m_actualCropWidth;
}
qreal actualCropHeight() const { qreal actualCropHeight() const { return m_actualCropHeight; }
return m_actualCropHeight;
}
qreal cropX() const { qreal cropX() const { return m_cropX; }
return m_cropX; void setCropX(qreal x);
}
void setCropX(qreal x);
qreal cropY() const { qreal cropY() const { return m_cropY; }
return m_cropY; void setCropY(qreal y);
}
void setCropY(qreal y);
qreal cropWidth() const { qreal cropWidth() const { return m_cropWidth; }
return m_cropWidth; void setCropWidth(qreal w);
}
void setCropWidth(qreal w);
qreal cropHeight() const { qreal cropHeight() const { return m_cropHeight; }
return m_cropHeight; void setCropHeight(qreal h);
}
void setCropHeight(qreal h);
protected: protected:
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) override; QSGNode* updatePaintNode(
QSGNode* oldNode, UpdatePaintNodeData* updatePaintNodeData) override;
signals: signals:
void sourceChanged(); void sourceChanged();
void screenResolutionChanged(); void screenResolutionChanged();
void zoomChanged(); void zoomChanged();
void actualCropChanged(); void actualCropChanged();
void cropXChanged(); void cropXChanged();
void cropYChanged(); void cropYChanged();
void cropWidthChanged(); void cropWidthChanged();
void cropHeightChanged(); void cropHeightChanged();
void statusChanged(); void statusChanged();
private: private:
void loadImage(); void loadImage();
void handleImageLoaded(); void handleImageLoaded();
QString getCacheFilePath() const; QString getCacheFilePath() const;
void setStatus(const Status s); void setStatus(const Status s);
Status m_status = Null; Status m_status = Null;
QUrl m_source; QUrl m_source;
QSize m_screenResolution; QSize m_screenResolution;
qreal m_zoom = 1.0; qreal m_zoom = 1.0;
qreal m_actualCropX = 0.0; qreal m_actualCropX = 0.0;
qreal m_actualCropY = 0.0; qreal m_actualCropY = 0.0;
qreal m_actualCropWidth = 1.0; qreal m_actualCropWidth = 1.0;
qreal m_actualCropHeight = 1.0; qreal m_actualCropHeight = 1.0;
qreal m_cropX = 0.0; qreal m_cropX = 0.0;
qreal m_cropY = 0.0; qreal m_cropY = 0.0;
qreal m_cropWidth = 1.0; qreal m_cropWidth = 1.0;
qreal m_cropHeight = 1.0; qreal m_cropHeight = 1.0;
QImage m_image; QImage m_image;
QSGTexture *m_texture = nullptr; QSGTexture* m_texture = nullptr;
bool m_textureDirty = false; bool m_textureDirty = false;
QFutureWatcher<QImage> m_imageWatcher; QFutureWatcher<QImage> m_imageWatcher;
}; };
} // namespace ZShell::internal } // namespace ZShell::internal
+349 -309
View File
@@ -6,474 +6,514 @@
namespace ZShell::models { namespace ZShell::models {
FileSystemEntry::FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent) FileSystemEntry::FileSystemEntry(
: QObject(parent) const QString& path, const QString& relativePath, QObject* parent)
, m_fileInfo(path) : QObject(parent)
, m_path(path) , m_fileInfo(path)
, m_relativePath(relativePath) , m_path(path)
, m_isImageInitialised(false) , m_relativePath(relativePath)
, m_mimeTypeInitialised(false) {} , m_isImageInitialised(false)
, m_mimeTypeInitialised(false) {}
QString FileSystemEntry::path() const { QString FileSystemEntry::path() const {
return m_path; return m_path;
}; };
QString FileSystemEntry::relativePath() const { QString FileSystemEntry::relativePath() const {
return m_relativePath; return m_relativePath;
}; };
QString FileSystemEntry::name() const { QString FileSystemEntry::name() const {
return m_fileInfo.fileName(); return m_fileInfo.fileName();
}; };
QString FileSystemEntry::baseName() const { QString FileSystemEntry::baseName() const {
return m_fileInfo.baseName(); return m_fileInfo.baseName();
}; };
QString FileSystemEntry::parentDir() const { QString FileSystemEntry::parentDir() const {
return m_fileInfo.absolutePath(); return m_fileInfo.absolutePath();
}; };
QString FileSystemEntry::suffix() const { QString FileSystemEntry::suffix() const {
return m_fileInfo.completeSuffix(); return m_fileInfo.completeSuffix();
}; };
qint64 FileSystemEntry::size() const { qint64 FileSystemEntry::size() const {
return m_fileInfo.size(); return m_fileInfo.size();
}; };
bool FileSystemEntry::isDir() const { bool FileSystemEntry::isDir() const {
return m_fileInfo.isDir(); return m_fileInfo.isDir();
}; };
bool FileSystemEntry::isImage() const { bool FileSystemEntry::isImage() const {
if (!m_isImageInitialised) { if (!m_isImageInitialised) {
QImageReader reader(m_path); QImageReader reader(m_path);
m_isImage = reader.canRead(); m_isImage = reader.canRead();
m_isImageInitialised = true; m_isImageInitialised = true;
} }
return m_isImage; return m_isImage;
} }
QString FileSystemEntry::mimeType() const { QString FileSystemEntry::mimeType() const {
if (!m_mimeTypeInitialised) { if (!m_mimeTypeInitialised) {
const QMimeDatabase db; const QMimeDatabase db;
m_mimeType = db.mimeTypeForFile(m_path).name(); m_mimeType = db.mimeTypeForFile(m_path).name();
m_mimeTypeInitialised = true; m_mimeTypeInitialised = true;
} }
return m_mimeType; return m_mimeType;
} }
void FileSystemEntry::updateRelativePath(const QDir& dir) { void FileSystemEntry::updateRelativePath(const QDir& dir) {
const auto relPath = dir.relativeFilePath(m_path); const auto relPath = dir.relativeFilePath(m_path);
if (m_relativePath != relPath) { if (m_relativePath != relPath) {
m_relativePath = relPath; m_relativePath = relPath;
emit relativePathChanged(); emit relativePathChanged();
} }
} }
FileSystemModel::FileSystemModel(QObject* parent) FileSystemModel::FileSystemModel(QObject* parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
, m_recursive(false) , m_recursive(false)
, m_watchChanges(true) , m_watchChanges(true)
, m_showHidden(false) , m_showHidden(false)
, m_filter(NoFilter) { , m_filter(NoFilter) {
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::watchDirIfRecursive); connect(
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::updateEntriesForDir); &m_watcher,
&QFileSystemWatcher::directoryChanged,
this,
&FileSystemModel::watchDirIfRecursive);
connect(
&m_watcher,
&QFileSystemWatcher::directoryChanged,
this,
&FileSystemModel::updateEntriesForDir);
} }
int FileSystemModel::rowCount(const QModelIndex& parent) const { int FileSystemModel::rowCount(const QModelIndex& parent) const {
if (parent != QModelIndex()) { if (parent != QModelIndex()) {
return 0; return 0;
} }
return static_cast<int>(m_entries.size()); return static_cast<int>(m_entries.size());
} }
QVariant FileSystemModel::data(const QModelIndex& index, int role) const { QVariant FileSystemModel::data(const QModelIndex& index, int role) const {
if (role != Qt::UserRole || !index.isValid() || index.row() >= m_entries.size()) { if (role != Qt::UserRole || !index.isValid() ||
return QVariant(); index.row() >= m_entries.size()) {
} return QVariant();
return QVariant::fromValue(m_entries.at(index.row())); }
return QVariant::fromValue(m_entries.at(index.row()));
} }
QHash<int, QByteArray> FileSystemModel::roleNames() const { QHash<int, QByteArray> FileSystemModel::roleNames() const {
return { { Qt::UserRole, "modelData" } }; return {{Qt::UserRole, "modelData"}};
} }
QString FileSystemModel::path() const { QString FileSystemModel::path() const {
return m_path; return m_path;
} }
void FileSystemModel::setPath(const QString& path) { void FileSystemModel::setPath(const QString& path) {
if (m_path == path) { if (m_path == path) {
return; return;
} }
m_path = path; m_path = path;
emit pathChanged(); emit pathChanged();
m_dir.setPath(m_path); m_dir.setPath(m_path);
for (const auto& entry : std::as_const(m_entries)) { for (const auto& entry : std::as_const(m_entries)) {
entry->updateRelativePath(m_dir); entry->updateRelativePath(m_dir);
} }
update(); update();
} }
bool FileSystemModel::recursive() const { bool FileSystemModel::recursive() const {
return m_recursive; return m_recursive;
} }
void FileSystemModel::setRecursive(bool recursive) { void FileSystemModel::setRecursive(bool recursive) {
if (m_recursive == recursive) { if (m_recursive == recursive) {
return; return;
} }
m_recursive = recursive; m_recursive = recursive;
emit recursiveChanged(); emit recursiveChanged();
update(); update();
} }
bool FileSystemModel::watchChanges() const { bool FileSystemModel::watchChanges() const {
return m_watchChanges; return m_watchChanges;
} }
void FileSystemModel::setWatchChanges(bool watchChanges) { void FileSystemModel::setWatchChanges(bool watchChanges) {
if (m_watchChanges == watchChanges) { if (m_watchChanges == watchChanges) {
return; return;
} }
m_watchChanges = watchChanges; m_watchChanges = watchChanges;
emit watchChangesChanged(); emit watchChangesChanged();
update(); update();
} }
bool FileSystemModel::showHidden() const { bool FileSystemModel::showHidden() const {
return m_showHidden; return m_showHidden;
} }
void FileSystemModel::setShowHidden(bool showHidden) { void FileSystemModel::setShowHidden(bool showHidden) {
if (m_showHidden == showHidden) { if (m_showHidden == showHidden) {
return; return;
} }
m_showHidden = showHidden; m_showHidden = showHidden;
emit showHiddenChanged(); emit showHiddenChanged();
update(); update();
} }
bool FileSystemModel::sortReverse() const { bool FileSystemModel::sortReverse() const {
return m_sortReverse; return m_sortReverse;
} }
void FileSystemModel::setSortReverse(bool sortReverse) { void FileSystemModel::setSortReverse(bool sortReverse) {
if (m_sortReverse == sortReverse) { if (m_sortReverse == sortReverse) {
return; return;
} }
m_sortReverse = sortReverse; m_sortReverse = sortReverse;
emit sortReverseChanged(); emit sortReverseChanged();
update(); update();
} }
FileSystemModel::Filter FileSystemModel::filter() const { FileSystemModel::Filter FileSystemModel::filter() const {
return m_filter; return m_filter;
} }
void FileSystemModel::setFilter(Filter filter) { void FileSystemModel::setFilter(Filter filter) {
if (m_filter == filter) { if (m_filter == filter) {
return; return;
} }
m_filter = filter; m_filter = filter;
emit filterChanged(); emit filterChanged();
update(); update();
} }
QStringList FileSystemModel::nameFilters() const { QStringList FileSystemModel::nameFilters() const {
return m_nameFilters; return m_nameFilters;
} }
void FileSystemModel::setNameFilters(const QStringList& nameFilters) { void FileSystemModel::setNameFilters(const QStringList& nameFilters) {
if (m_nameFilters == nameFilters) { if (m_nameFilters == nameFilters) {
return; return;
} }
m_nameFilters = nameFilters; m_nameFilters = nameFilters;
emit nameFiltersChanged(); emit nameFiltersChanged();
update(); update();
} }
QQmlListProperty<FileSystemEntry> FileSystemModel::entries() { QQmlListProperty<FileSystemEntry> FileSystemModel::entries() {
return QQmlListProperty<FileSystemEntry>(this, &m_entries); return QQmlListProperty<FileSystemEntry>(this, &m_entries);
} }
void FileSystemModel::watchDirIfRecursive(const QString& path) { void FileSystemModel::watchDirIfRecursive(const QString& path) {
if (m_recursive && m_watchChanges) { if (m_recursive && m_watchChanges) {
const auto currentDir = m_dir; const auto currentDir = m_dir;
const bool showHidden = m_showHidden; const bool showHidden = m_showHidden;
const auto future = QtConcurrent::run([showHidden, path]() { const auto future = QtConcurrent::run([showHidden, path]() {
QDir::Filters filters = QDir::Dirs | QDir::NoDotAndDotDot; QDir::Filters filters = QDir::Dirs | QDir::NoDotAndDotDot;
if (showHidden) { if (showHidden) {
filters |= QDir::Hidden; filters |= QDir::Hidden;
} }
QDirIterator iter(path, filters, QDirIterator::Subdirectories); QDirIterator iter(path, filters, QDirIterator::Subdirectories);
QStringList dirs; QStringList dirs;
while (iter.hasNext()) { while (iter.hasNext()) {
dirs << iter.next(); dirs << iter.next();
} }
return dirs; return dirs;
}); });
const auto watcher = new QFutureWatcher<QStringList>(this); const auto watcher = new QFutureWatcher<QStringList>(this);
connect(watcher, &QFutureWatcher<QStringList>::finished, this, [currentDir, showHidden, watcher, this]() { connect(
const auto paths = watcher->result(); watcher,
if (currentDir == m_dir && showHidden == m_showHidden && !paths.isEmpty()) { &QFutureWatcher<QStringList>::finished,
// Ignore if dir or showHidden has changed this,
m_watcher.addPaths(paths); [currentDir, showHidden, watcher, this]() {
} const auto paths = watcher->result();
watcher->deleteLater(); if (currentDir == m_dir && showHidden == m_showHidden &&
}); !paths.isEmpty()) {
watcher->setFuture(future); // Ignore if dir or showHidden has changed
} m_watcher.addPaths(paths);
}
watcher->deleteLater();
});
watcher->setFuture(future);
}
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
} }
void FileSystemModel::update() { void FileSystemModel::update() {
updateWatcher(); updateWatcher();
updateEntries(); updateEntries();
} }
void FileSystemModel::updateWatcher() { void FileSystemModel::updateWatcher() {
if (!m_watcher.directories().isEmpty()) { if (!m_watcher.directories().isEmpty()) {
m_watcher.removePaths(m_watcher.directories()); m_watcher.removePaths(m_watcher.directories());
} }
if (!m_watchChanges || m_path.isEmpty()) { if (!m_watchChanges || m_path.isEmpty()) {
return; return;
} }
m_watcher.addPath(m_path); m_watcher.addPath(m_path);
watchDirIfRecursive(m_path); watchDirIfRecursive(m_path);
} }
void FileSystemModel::updateEntries() { void FileSystemModel::updateEntries() {
if (m_path.isEmpty()) { if (m_path.isEmpty()) {
if (!m_entries.isEmpty()) { if (!m_entries.isEmpty()) {
beginResetModel(); beginResetModel();
qDeleteAll(m_entries); qDeleteAll(m_entries);
m_entries.clear(); m_entries.clear();
endResetModel(); endResetModel();
emit entriesChanged(); emit entriesChanged();
} }
return; return;
} }
for (auto& future : m_futures) { for (auto& future : m_futures) {
future.cancel(); future.cancel();
} }
m_futures.clear(); m_futures.clear();
updateEntriesForDir(m_path); updateEntriesForDir(m_path);
} }
void FileSystemModel::updateEntriesForDir(const QString& dir) { void FileSystemModel::updateEntriesForDir(const QString& dir) {
const auto recursive = m_recursive; const auto recursive = m_recursive;
const auto showHidden = m_showHidden; const auto showHidden = m_showHidden;
const auto filter = m_filter; const auto filter = m_filter;
const auto nameFilters = m_nameFilters; const auto nameFilters = m_nameFilters;
QSet<QString> oldPaths; QSet<QString> oldPaths;
for (const auto& entry : std::as_const(m_entries)) { for (const auto& entry : std::as_const(m_entries)) {
oldPaths << entry->path(); oldPaths << entry->path();
} }
const auto future = QtConcurrent::run([=](QPromise<QPair<QSet<QString>, QSet<QString>>>& promise) { const auto future = QtConcurrent::run(
const auto flags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags; [=](QPromise<QPair<QSet<QString>, QSet<QString>>>& promise) {
const auto flags = recursive ? QDirIterator::Subdirectories
: QDirIterator::NoIteratorFlags;
std::optional<QDirIterator> iter; std::optional<QDirIterator> iter;
if (filter == Images) { if (filter == Images) {
QStringList extraNameFilters = nameFilters; QStringList extraNameFilters = nameFilters;
const auto formats = QImageReader::supportedImageFormats(); const auto formats = QImageReader::supportedImageFormats();
for (const auto& format : formats) { for (const auto& format : formats) {
extraNameFilters << "*." + format; extraNameFilters << "*." + format;
} }
QDir::Filters filters = QDir::Files; QDir::Filters filters = QDir::Files;
if (showHidden) { if (showHidden) {
filters |= QDir::Hidden; filters |= QDir::Hidden;
} }
iter.emplace(dir, extraNameFilters, filters, flags); iter.emplace(dir, extraNameFilters, filters, flags);
} else { } else {
QDir::Filters filters; QDir::Filters filters;
if (filter == Files) { if (filter == Files) {
filters = QDir::Files; filters = QDir::Files;
} else if (filter == Dirs) { } else if (filter == Dirs) {
filters = QDir::Dirs | QDir::NoDotAndDotDot; filters = QDir::Dirs | QDir::NoDotAndDotDot;
} else { } else {
filters = QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot; filters = QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot;
} }
if (showHidden) { if (showHidden) {
filters |= QDir::Hidden; filters |= QDir::Hidden;
} }
if (nameFilters.isEmpty()) { if (nameFilters.isEmpty()) {
iter.emplace(dir, filters, flags); iter.emplace(dir, filters, flags);
} else { } else {
iter.emplace(dir, nameFilters, filters, flags); iter.emplace(dir, nameFilters, filters, flags);
} }
} }
QSet<QString> newPaths; QSet<QString> newPaths;
while (iter->hasNext()) { while (iter->hasNext()) {
if (promise.isCanceled()) { if (promise.isCanceled()) {
return; return;
} }
QString path = iter->next(); QString path = iter->next();
if (filter == Images) { if (filter == Images) {
QImageReader reader(path); QImageReader reader(path);
if (!reader.canRead()) { if (!reader.canRead()) {
continue; continue;
} }
} }
newPaths.insert(path); newPaths.insert(path);
} }
if (promise.isCanceled() || newPaths == oldPaths) { if (promise.isCanceled() || newPaths == oldPaths) {
return; return;
} }
promise.addResult(qMakePair(oldPaths - newPaths, newPaths - oldPaths)); promise.addResult(
}); qMakePair(oldPaths - newPaths, newPaths - oldPaths));
});
if (m_futures.contains(dir)) { if (m_futures.contains(dir)) {
m_futures[dir].cancel(); m_futures[dir].cancel();
} }
m_futures.insert(dir, future); m_futures.insert(dir, future);
const auto watcher = new QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>(this); const auto watcher =
new QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>(this);
connect(watcher, &QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>::finished, this, [dir, watcher, this]() { connect(
m_futures.remove(dir); watcher,
&QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>::finished,
this,
[dir, watcher, this]() {
m_futures.remove(dir);
if (!watcher->future().isResultReadyAt(0)) { if (!watcher->future().isResultReadyAt(0)) {
watcher->deleteLater(); watcher->deleteLater();
return; return;
} }
const auto result = watcher->result(); const auto result = watcher->result();
applyChanges(result.first, result.second); applyChanges(result.first, result.second);
watcher->deleteLater(); watcher->deleteLater();
}); });
watcher->setFuture(future); watcher->setFuture(future);
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
} }
void FileSystemModel::applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths) { void FileSystemModel::applyChanges(
QList<int> removedIndices; const QSet<QString>& removedPaths, const QSet<QString>& addedPaths) {
for (int i = 0; i < m_entries.size(); ++i) { QList<int> removedIndices;
if (removedPaths.contains(m_entries[i]->path())) { for (int i = 0; i < m_entries.size(); ++i) {
removedIndices << i; if (removedPaths.contains(m_entries[i]->path())) {
} removedIndices << i;
} }
std::sort(removedIndices.begin(), removedIndices.end(), std::greater<int>()); }
std::sort(removedIndices.begin(), removedIndices.end(), std::greater<int>());
// Batch remove old entries // Batch remove old entries
int start = -1; int start = -1;
int end = -1; int end = -1;
for (int idx : std::as_const(removedIndices)) { for (int idx : std::as_const(removedIndices)) {
if (start == -1) { if (start == -1) {
start = idx; start = idx;
end = idx; end = idx;
} else if (idx == end - 1) { } else if (idx == end - 1) {
end = idx; end = idx;
} else { } else {
beginRemoveRows(QModelIndex(), end, start); beginRemoveRows(QModelIndex(), end, start);
for (int i = start; i >= end; --i) { for (int i = start; i >= end; --i) {
m_entries.takeAt(i)->deleteLater(); m_entries.takeAt(i)->deleteLater();
} }
endRemoveRows(); endRemoveRows();
start = idx; start = idx;
end = idx; end = idx;
} }
} }
if (start != -1) { if (start != -1) {
beginRemoveRows(QModelIndex(), end, start); beginRemoveRows(QModelIndex(), end, start);
for (int i = start; i >= end; --i) { for (int i = start; i >= end; --i) {
m_entries.takeAt(i)->deleteLater(); m_entries.takeAt(i)->deleteLater();
} }
endRemoveRows(); endRemoveRows();
} }
// Create new entries // Create new entries
QList<FileSystemEntry*> newEntries; QList<FileSystemEntry*> newEntries;
for (const auto& path : addedPaths) { for (const auto& path : addedPaths) {
newEntries << new FileSystemEntry(path, m_dir.relativeFilePath(path), this); newEntries << new FileSystemEntry(
} path, m_dir.relativeFilePath(path), this);
std::sort(newEntries.begin(), newEntries.end(), [this](const FileSystemEntry* a, const FileSystemEntry* b) { }
return compareEntries(a, b); std::sort(
}); newEntries.begin(),
newEntries.end(),
[this](const FileSystemEntry* a, const FileSystemEntry* b) {
return compareEntries(a, b);
});
// Batch insert new entries // Batch insert new entries
int insertStart = -1; int insertStart = -1;
QList<FileSystemEntry*> batchItems; QList<FileSystemEntry*> batchItems;
for (const auto& entry : std::as_const(newEntries)) { for (const auto& entry : std::as_const(newEntries)) {
const auto it = std::lower_bound( const auto it = std::lower_bound(
m_entries.begin(), m_entries.end(), entry, [this](const FileSystemEntry* a, const FileSystemEntry* b) { m_entries.begin(),
return compareEntries(a, b); m_entries.end(),
}); entry,
const auto row = static_cast<int>(it - m_entries.begin()); [this](const FileSystemEntry* a, const FileSystemEntry* b) {
return compareEntries(a, b);
});
const auto row = static_cast<int>(it - m_entries.begin());
if (insertStart == -1) { if (insertStart == -1) {
insertStart = row; insertStart = row;
batchItems << entry; batchItems << entry;
} else if (row == insertStart + batchItems.size()) { } else if (row == insertStart + batchItems.size()) {
batchItems << entry; batchItems << entry;
} else { } else {
beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast<int>(batchItems.size()) - 1); beginInsertRows(
for (int i = 0; i < batchItems.size(); ++i) { QModelIndex(),
m_entries.insert(insertStart + i, batchItems[i]); insertStart,
} insertStart + static_cast<int>(batchItems.size()) - 1);
endInsertRows(); for (int i = 0; i < batchItems.size(); ++i) {
m_entries.insert(insertStart + i, batchItems[i]);
}
endInsertRows();
insertStart = row; insertStart = row;
batchItems.clear(); batchItems.clear();
batchItems << entry; batchItems << entry;
} }
} }
if (!batchItems.isEmpty()) { if (!batchItems.isEmpty()) {
beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast<int>(batchItems.size()) - 1); beginInsertRows(
for (int i = 0; i < batchItems.size(); ++i) { QModelIndex(),
m_entries.insert(insertStart + i, batchItems[i]); insertStart,
} insertStart + static_cast<int>(batchItems.size()) - 1);
endInsertRows(); for (int i = 0; i < batchItems.size(); ++i) {
} m_entries.insert(insertStart + i, batchItems[i]);
}
endInsertRows();
}
emit entriesChanged(); emit entriesChanged();
} }
bool FileSystemModel::compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const { bool FileSystemModel::compareEntries(
if (a->isDir() != b->isDir()) { const FileSystemEntry* a, const FileSystemEntry* b) const {
return m_sortReverse ^ a->isDir(); if (a->isDir() != b->isDir()) {
} return m_sortReverse ^ a->isDir();
const auto cmp = a->relativePath().localeAwareCompare(b->relativePath()); }
return m_sortReverse ? cmp > 0 : cmp < 0; const auto cmp = a->relativePath().localeAwareCompare(b->relativePath());
return m_sortReverse ? cmp > 0 : cmp < 0;
} }
} // namespace ZShell::models } // namespace ZShell::models
+116 -102
View File
@@ -1,6 +1,5 @@
#pragma once #pragma once
#include <cstdint>
#include <qabstractitemmodel.h> #include <qabstractitemmodel.h>
#include <qdir.h> #include <qdir.h>
#include <qfilesystemwatcher.h> #include <qfilesystemwatcher.h>
@@ -14,136 +13,151 @@
namespace ZShell::models { namespace ZShell::models {
class FileSystemEntry : public QObject { class FileSystemEntry : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("FileSystemEntry instances can only be retrieved from a FileSystemModel") QML_UNCREATABLE(
"FileSystemEntry instances can only be retrieved from a "
"FileSystemModel")
Q_PROPERTY(QString path READ path CONSTANT) Q_PROPERTY(QString path READ path CONSTANT)
Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged) Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged)
Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(QString baseName READ baseName CONSTANT) Q_PROPERTY(QString baseName READ baseName CONSTANT)
Q_PROPERTY(QString parentDir READ parentDir CONSTANT) Q_PROPERTY(QString parentDir READ parentDir CONSTANT)
Q_PROPERTY(QString suffix READ suffix CONSTANT) Q_PROPERTY(QString suffix READ suffix CONSTANT)
Q_PROPERTY(qint64 size READ size CONSTANT) Q_PROPERTY(qint64 size READ size CONSTANT)
Q_PROPERTY(bool isDir READ isDir CONSTANT) Q_PROPERTY(bool isDir READ isDir CONSTANT)
Q_PROPERTY(bool isImage READ isImage CONSTANT) Q_PROPERTY(bool isImage READ isImage CONSTANT)
Q_PROPERTY(QString mimeType READ mimeType CONSTANT) Q_PROPERTY(QString mimeType READ mimeType CONSTANT)
public: public:
explicit FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent = nullptr); explicit FileSystemEntry(
const QString& path,
const QString& relativePath,
QObject* parent = nullptr);
[[nodiscard]] QString path() const; [[nodiscard]] QString path() const;
[[nodiscard]] QString relativePath() const; [[nodiscard]] QString relativePath() const;
[[nodiscard]] QString name() const; [[nodiscard]] QString name() const;
[[nodiscard]] QString baseName() const; [[nodiscard]] QString baseName() const;
[[nodiscard]] QString parentDir() const; [[nodiscard]] QString parentDir() const;
[[nodiscard]] QString suffix() const; [[nodiscard]] QString suffix() const;
[[nodiscard]] qint64 size() const; [[nodiscard]] qint64 size() const;
[[nodiscard]] bool isDir() const; [[nodiscard]] bool isDir() const;
[[nodiscard]] bool isImage() const; [[nodiscard]] bool isImage() const;
[[nodiscard]] QString mimeType() const; [[nodiscard]] QString mimeType() const;
void updateRelativePath(const QDir& dir); void updateRelativePath(const QDir& dir);
signals: signals:
void relativePathChanged(); void relativePathChanged();
private: private:
const QFileInfo m_fileInfo; const QFileInfo m_fileInfo;
const QString m_path; const QString m_path;
QString m_relativePath; QString m_relativePath;
mutable bool m_isImage; mutable bool m_isImage;
mutable bool m_isImageInitialised; mutable bool m_isImageInitialised;
mutable QString m_mimeType; mutable QString m_mimeType;
mutable bool m_mimeTypeInitialised; mutable bool m_mimeTypeInitialised;
}; };
class FileSystemModel : public QAbstractListModel { class FileSystemModel : public QAbstractListModel {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged) Q_PROPERTY(
Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged) bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged) Q_PROPERTY(
Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged) bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged) watchChangesChanged)
Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged) Q_PROPERTY(
bool showHidden READ showHidden WRITE setShowHidden NOTIFY
showHiddenChanged)
Q_PROPERTY(
bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY
sortReverseChanged)
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
Q_PROPERTY(
QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY
nameFiltersChanged)
Q_PROPERTY(QQmlListProperty<ZShell::models::FileSystemEntry> entries READ entries NOTIFY entriesChanged) Q_PROPERTY(
QQmlListProperty<ZShell::models::FileSystemEntry> entries READ entries
NOTIFY entriesChanged)
public: public:
enum Filter { enum Filter { NoFilter, Images, Files, Dirs };
NoFilter, Q_ENUM(Filter)
Images,
Files,
Dirs
};
Q_ENUM(Filter)
explicit FileSystemModel(QObject* parent = nullptr); explicit FileSystemModel(QObject* parent = nullptr);
[[nodiscard]] int rowCount(const QModelIndex& parent = QModelIndex()) const override; [[nodiscard]] int rowCount(
[[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; const QModelIndex& parent = QModelIndex()) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override; [[nodiscard]] QVariant data(
const QModelIndex& index, int role = Qt::DisplayRole) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
[[nodiscard]] QString path() const; [[nodiscard]] QString path() const;
void setPath(const QString& path); void setPath(const QString& path);
[[nodiscard]] bool recursive() const; [[nodiscard]] bool recursive() const;
void setRecursive(bool recursive); void setRecursive(bool recursive);
[[nodiscard]] bool watchChanges() const; [[nodiscard]] bool watchChanges() const;
void setWatchChanges(bool watchChanges); void setWatchChanges(bool watchChanges);
[[nodiscard]] bool showHidden() const; [[nodiscard]] bool showHidden() const;
void setShowHidden(bool showHidden); void setShowHidden(bool showHidden);
[[nodiscard]] bool sortReverse() const; [[nodiscard]] bool sortReverse() const;
void setSortReverse(bool sortReverse); void setSortReverse(bool sortReverse);
[[nodiscard]] Filter filter() const; [[nodiscard]] Filter filter() const;
void setFilter(Filter filter); void setFilter(Filter filter);
[[nodiscard]] QStringList nameFilters() const; [[nodiscard]] QStringList nameFilters() const;
void setNameFilters(const QStringList& nameFilters); void setNameFilters(const QStringList& nameFilters);
[[nodiscard]] QQmlListProperty<FileSystemEntry> entries(); [[nodiscard]] QQmlListProperty<FileSystemEntry> entries();
signals: signals:
void pathChanged(); void pathChanged();
void recursiveChanged(); void recursiveChanged();
void watchChangesChanged(); void watchChangesChanged();
void showHiddenChanged(); void showHiddenChanged();
void sortReverseChanged(); void sortReverseChanged();
void filterChanged(); void filterChanged();
void nameFiltersChanged(); void nameFiltersChanged();
void entriesChanged(); void entriesChanged();
private: private:
QDir m_dir; QDir m_dir;
QFileSystemWatcher m_watcher; QFileSystemWatcher m_watcher;
QList<FileSystemEntry*> m_entries; QList<FileSystemEntry*> m_entries;
QHash<QString, QFuture<QPair<QSet<QString>, QSet<QString> > > > m_futures; QHash<QString, QFuture<QPair<QSet<QString>, QSet<QString>>>> m_futures;
QString m_path; QString m_path;
bool m_recursive; bool m_recursive;
bool m_watchChanges; bool m_watchChanges;
bool m_showHidden; bool m_showHidden;
bool m_sortReverse; bool m_sortReverse;
Filter m_filter; Filter m_filter;
QStringList m_nameFilters; QStringList m_nameFilters;
void watchDirIfRecursive(const QString& path); void watchDirIfRecursive(const QString& path);
void update(); void update();
void updateWatcher(); void updateWatcher();
void updateEntries(); void updateEntries();
void updateEntriesForDir(const QString& dir); void updateEntriesForDir(const QString& dir);
void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths); void applyChanges(
[[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const; const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
[[nodiscard]] bool compareEntries(
const FileSystemEntry* a, const FileSystemEntry* b) const;
}; };
} // namespace ZShell::models } // namespace ZShell::models
+65 -34
View File
@@ -23,20 +23,33 @@ PipeWireWorker::PipeWireWorker(std::stop_token token, AudioCollector* collector)
m_loop = pw_main_loop_new(nullptr); m_loop = pw_main_loop_new(nullptr);
if (!m_loop) { if (!m_loop) {
qWarning() << "PipeWireWorker::init: failed to create PipeWire main loop"; qWarning()
<< "PipeWireWorker::init: failed to create PipeWire main loop";
pw_deinit(); pw_deinit();
return; return;
} }
timespec timeout = { 0, 10 * SPA_NSEC_PER_MSEC }; timespec timeout = {0, 10 * SPA_NSEC_PER_MSEC};
m_timer = pw_loop_add_timer(pw_main_loop_get_loop(m_loop), handleTimeout, this); m_timer =
pw_loop_update_timer(pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false); pw_loop_add_timer(pw_main_loop_get_loop(m_loop), handleTimeout, this);
pw_loop_update_timer(
pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false);
auto props = pw_properties_new( auto props = pw_properties_new(
PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Music", nullptr); PW_KEY_MEDIA_TYPE,
"Audio",
PW_KEY_MEDIA_CATEGORY,
"Capture",
PW_KEY_MEDIA_ROLE,
"Music",
nullptr);
pw_properties_set(props, PW_KEY_STREAM_CAPTURE_SINK, "true"); pw_properties_set(props, PW_KEY_STREAM_CAPTURE_SINK, "true");
pw_properties_setf( pw_properties_setf(
props, PW_KEY_NODE_LATENCY, "%u/%u", nextPowerOf2(512 * ac::SAMPLE_RATE / 48000), ac::SAMPLE_RATE); props,
PW_KEY_NODE_LATENCY,
"%u/%u",
nextPowerOf2(512 * ac::SAMPLE_RATE / 48000),
ac::SAMPLE_RATE);
pw_properties_set(props, PW_KEY_NODE_PASSIVE, "true"); pw_properties_set(props, PW_KEY_NODE_PASSIVE, "true");
pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
pw_properties_set(props, PW_KEY_STREAM_DONT_REMIX, "false"); pw_properties_set(props, PW_KEY_STREAM_DONT_REMIX, "false");
@@ -55,21 +68,28 @@ PipeWireWorker::PipeWireWorker(std::stop_token token, AudioCollector* collector)
params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info); params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info);
pw_stream_events events{}; pw_stream_events events{};
events.state_changed = [](void* data, pw_stream_state, pw_stream_state state, const char*) { events.state_changed =
auto* self = static_cast<PipeWireWorker*>(data); [](void* data, pw_stream_state, pw_stream_state state, const char*) {
self->streamStateChanged(state); auto* self = static_cast<PipeWireWorker*>(data);
}; self->streamStateChanged(state);
};
events.process = [](void* data) { events.process = [](void* data) {
auto* self = static_cast<PipeWireWorker*>(data); auto* self = static_cast<PipeWireWorker*>(data);
self->processStream(); self->processStream();
}; };
m_stream = pw_stream_new_simple(pw_main_loop_get_loop(m_loop), "ZShell-shell", props, &events, this); m_stream = pw_stream_new_simple(
pw_main_loop_get_loop(m_loop), "ZShell-shell", props, &events, this);
const int success = pw_stream_connect(m_stream, PW_DIRECTION_INPUT, PW_ID_ANY, const int success = pw_stream_connect(
static_cast<pw_stream_flags>( m_stream,
PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS), PW_DIRECTION_INPUT,
params, 1); PW_ID_ANY,
static_cast<pw_stream_flags>(
PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS |
PW_STREAM_FLAG_RT_PROCESS),
params,
1);
if (success < 0) { if (success < 0) {
qWarning() << "PipeWireWorker::init: failed to connect stream"; qWarning() << "PipeWireWorker::init: failed to connect stream";
pw_stream_destroy(m_stream); pw_stream_destroy(m_stream);
@@ -98,8 +118,13 @@ void PipeWireWorker::handleTimeout(void* data, uint64_t expirations) {
self->m_collector->clearBuffer(); self->m_collector->clearBuffer();
} else { } else {
self->m_idle = true; self->m_idle = true;
timespec timeout = { 0, 500 * SPA_NSEC_PER_MSEC }; timespec timeout = {0, 500 * SPA_NSEC_PER_MSEC};
pw_loop_update_timer(pw_main_loop_get_loop(self->m_loop), self->m_timer, &timeout, &timeout, false); pw_loop_update_timer(
pw_main_loop_get_loop(self->m_loop),
self->m_timer,
&timeout,
&timeout,
false);
} }
} }
} }
@@ -108,12 +133,14 @@ void PipeWireWorker::streamStateChanged(pw_stream_state state) {
m_idle = false; m_idle = false;
switch (state) { switch (state) {
case PW_STREAM_STATE_PAUSED: { case PW_STREAM_STATE_PAUSED: {
timespec timeout = { 0, 10 * SPA_NSEC_PER_MSEC }; timespec timeout = {0, 10 * SPA_NSEC_PER_MSEC};
pw_loop_update_timer(pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false); pw_loop_update_timer(
pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false);
break; break;
} }
case PW_STREAM_STATE_STREAMING: case PW_STREAM_STATE_STREAMING:
pw_loop_update_timer(pw_main_loop_get_loop(m_loop), m_timer, nullptr, nullptr, false); pw_loop_update_timer(
pw_main_loop_get_loop(m_loop), m_timer, nullptr, nullptr, false);
break; break;
case PW_STREAM_STATE_ERROR: case PW_STREAM_STATE_ERROR:
pw_main_loop_quit(m_loop); pw_main_loop_quit(m_loop);
@@ -171,7 +198,8 @@ void AudioCollector::clearBuffer() {
auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed); auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed);
std::fill(writeBuffer->begin(), writeBuffer->end(), 0.0f); std::fill(writeBuffer->begin(), writeBuffer->end(), 0.0f);
auto* oldRead = m_readBuffer.exchange(writeBuffer, std::memory_order_acq_rel); auto* oldRead =
m_readBuffer.exchange(writeBuffer, std::memory_order_acq_rel);
m_writeBuffer.store(oldRead, std::memory_order_release); m_writeBuffer.store(oldRead, std::memory_order_release);
} }
@@ -181,11 +209,13 @@ void AudioCollector::loadChunk(const qint16* samples, quint32 count) {
} }
auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed); auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed);
std::transform(samples, samples + count, writeBuffer->begin(), [](qint16 sample) { std::transform(
samples, samples + count, writeBuffer->begin(), [](qint16 sample) {
return sample / 32768.0f; return sample / 32768.0f;
}); });
auto* oldRead = m_readBuffer.exchange(writeBuffer, std::memory_order_acq_rel); auto* oldRead =
m_readBuffer.exchange(writeBuffer, std::memory_order_acq_rel);
m_writeBuffer.store(oldRead, std::memory_order_release); m_writeBuffer.store(oldRead, std::memory_order_release);
} }
@@ -206,9 +236,11 @@ quint32 AudioCollector::readChunk(double* out, quint32 count) {
} }
auto* readBuffer = m_readBuffer.load(std::memory_order_acquire); auto* readBuffer = m_readBuffer.load(std::memory_order_acquire);
std::transform(readBuffer->begin(), readBuffer->begin() + count, out, [](float sample) { std::transform(
return static_cast<double>(sample); readBuffer->begin(),
}); readBuffer->begin() + count,
out,
[](float sample) { return static_cast<double>(sample); });
return count; return count;
} }
@@ -218,10 +250,10 @@ AudioCollector::AudioCollector(QObject* parent)
, m_buffer1(ac::CHUNK_SIZE) , m_buffer1(ac::CHUNK_SIZE)
, m_buffer2(ac::CHUNK_SIZE) , m_buffer2(ac::CHUNK_SIZE)
, m_readBuffer(&m_buffer1) , m_readBuffer(&m_buffer1)
, m_writeBuffer(&m_buffer2) { , m_writeBuffer(&m_buffer2) {}
}
AudioCollector::~AudioCollector() { AudioCollector::~AudioCollector() {
// NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.VirtualCall)
stop(); stop();
} }
@@ -232,9 +264,8 @@ void AudioCollector::start() {
clearBuffer(); clearBuffer();
m_thread = std::jthread([this](std::stop_token token) { m_thread = std::jthread(
PipeWireWorker worker(token, this); [this](std::stop_token token) { PipeWireWorker worker(token, this); });
});
} }
void AudioCollector::stop() { void AudioCollector::stop() {
+35 -35
View File
@@ -22,55 +22,55 @@ constexpr quint32 CHUNK_SIZE = 512;
class AudioCollector; class AudioCollector;
class PipeWireWorker { class PipeWireWorker {
public: public:
explicit PipeWireWorker(std::stop_token token, AudioCollector* collector); explicit PipeWireWorker(std::stop_token token, AudioCollector* collector);
void run(); void run();
private: private:
pw_main_loop* m_loop; pw_main_loop* m_loop;
pw_stream* m_stream; pw_stream* m_stream;
spa_source* m_timer; spa_source* m_timer;
bool m_idle; bool m_idle;
std::stop_token m_token; std::stop_token m_token;
AudioCollector* m_collector; AudioCollector* m_collector;
static void handleTimeout(void* data, uint64_t expirations); static void handleTimeout(void* data, uint64_t expirations);
void streamStateChanged(pw_stream_state state); void streamStateChanged(pw_stream_state state);
void processStream(); void processStream();
[[nodiscard]] unsigned int nextPowerOf2(unsigned int n); [[nodiscard]] unsigned int nextPowerOf2(unsigned int n);
}; };
class AudioCollector : public Service { class AudioCollector : public Service {
Q_OBJECT Q_OBJECT
public: public:
AudioCollector(const AudioCollector&) = delete; AudioCollector(const AudioCollector&) = delete;
AudioCollector& operator=(const AudioCollector&) = delete; AudioCollector& operator=(const AudioCollector&) = delete;
static AudioCollector& instance(); static AudioCollector& instance();
void clearBuffer(); void clearBuffer();
void loadChunk(const qint16* samples, quint32 count); void loadChunk(const qint16* samples, quint32 count);
quint32 readChunk(float* out, quint32 count = 0); quint32 readChunk(float* out, quint32 count = 0);
quint32 readChunk(double* out, quint32 count = 0); quint32 readChunk(double* out, quint32 count = 0);
private: private:
explicit AudioCollector(QObject* parent = nullptr); explicit AudioCollector(QObject* parent = nullptr);
~AudioCollector() override; ~AudioCollector() override;
std::jthread m_thread; std::jthread m_thread;
std::vector<float> m_buffer1; std::vector<float> m_buffer1;
std::vector<float> m_buffer2; std::vector<float> m_buffer2;
std::atomic<std::vector<float>*> m_readBuffer; std::atomic<std::vector<float>*> m_readBuffer;
std::atomic<std::vector<float>*> m_writeBuffer; std::atomic<std::vector<float>*> m_writeBuffer;
quint32 m_sampleCount; quint32 m_sampleCount;
void reload(); void reload();
void start() override; void start() override;
void stop() override; void stop() override;
}; };
} // namespace ZShell::services } // namespace ZShell::services
+18 -12
View File
@@ -7,9 +7,7 @@
namespace ZShell::services { namespace ZShell::services {
AudioProcessor::AudioProcessor(QObject* parent) AudioProcessor::AudioProcessor(QObject* parent) : QObject(parent) {}
: QObject(parent) {
}
AudioProcessor::~AudioProcessor() { AudioProcessor::~AudioProcessor() {
stop(); stop();
@@ -17,12 +15,17 @@ AudioProcessor::~AudioProcessor() {
void AudioProcessor::init() { void AudioProcessor::init() {
m_timer = new QTimer(this); m_timer = new QTimer(this);
m_timer->setInterval(static_cast<int>(ac::CHUNK_SIZE * 1000.0 / ac::SAMPLE_RATE)); m_timer->setInterval(
static_cast<int>(ac::CHUNK_SIZE * 1000.0 / ac::SAMPLE_RATE));
connect(m_timer, &QTimer::timeout, this, &AudioProcessor::process); connect(m_timer, &QTimer::timeout, this, &AudioProcessor::process);
} }
void AudioProcessor::start() { void AudioProcessor::start() {
QMetaObject::invokeMethod(&AudioCollector::instance(), &AudioCollector::ref, Qt::QueuedConnection, this); QMetaObject::invokeMethod(
&AudioCollector::instance(),
&AudioCollector::ref,
Qt::QueuedConnection,
this);
if (m_timer) { if (m_timer) {
m_timer->start(); m_timer->start();
} }
@@ -32,14 +35,15 @@ void AudioProcessor::stop() {
if (m_timer) { if (m_timer) {
m_timer->stop(); m_timer->stop();
} }
QMetaObject::invokeMethod(&AudioCollector::instance(), &AudioCollector::unref, Qt::QueuedConnection, this); QMetaObject::invokeMethod(
&AudioCollector::instance(),
&AudioCollector::unref,
Qt::QueuedConnection,
this);
} }
AudioProvider::AudioProvider(QObject* parent) AudioProvider::AudioProvider(QObject* parent)
: Service(parent) : Service(parent), m_processor(nullptr), m_thread(nullptr) {}
, m_processor(nullptr)
, m_thread(nullptr) {
}
AudioProvider::~AudioProvider() { AudioProvider::~AudioProvider() {
if (m_thread) { if (m_thread) {
@@ -50,7 +54,8 @@ AudioProvider::~AudioProvider() {
void AudioProvider::init() { void AudioProvider::init() {
if (!m_processor) { if (!m_processor) {
qWarning() << "AudioProvider::init: attempted to init with no processor set"; qWarning()
<< "AudioProvider::init: attempted to init with no processor set";
return; return;
} }
@@ -58,7 +63,8 @@ void AudioProvider::init() {
m_processor->moveToThread(m_thread); m_processor->moveToThread(m_thread);
connect(m_thread, &QThread::started, m_processor, &AudioProcessor::init); connect(m_thread, &QThread::started, m_processor, &AudioProcessor::init);
connect(m_thread, &QThread::finished, m_processor, &AudioProcessor::deleteLater); connect(
m_thread, &QThread::finished, m_processor, &AudioProcessor::deleteLater);
connect(m_thread, &QThread::finished, m_thread, &QThread::deleteLater); connect(m_thread, &QThread::finished, m_thread, &QThread::deleteLater);
m_thread->start(); m_thread->start();
+22 -22
View File
@@ -7,41 +7,41 @@
namespace ZShell::services { namespace ZShell::services {
class AudioProcessor : public QObject { class AudioProcessor : public QObject {
Q_OBJECT Q_OBJECT
public: public:
explicit AudioProcessor(QObject* parent = nullptr); explicit AudioProcessor(QObject* parent = nullptr);
~AudioProcessor() override; ~AudioProcessor() override;
void init(); void init();
void start(); void start();
void stop(); void stop();
protected: protected:
virtual void process() = 0; virtual void process() = 0;
private: private:
QTimer* m_timer; QTimer* m_timer;
}; };
class AudioProvider : public Service { class AudioProvider : public Service {
Q_OBJECT Q_OBJECT
public: public:
explicit AudioProvider(QObject* parent = nullptr); explicit AudioProvider(QObject* parent = nullptr);
~AudioProvider() override; ~AudioProvider() override;
protected: protected:
AudioProcessor* m_processor; AudioProcessor* m_processor;
void init(); void init();
private: private:
QThread* m_thread; QThread* m_thread;
void start() override; void start() override;
void stop() override; void stop() override;
}; };
} // namespace ZShell::services } // namespace ZShell::services
+7 -6
View File
@@ -10,8 +10,7 @@ BeatProcessor::BeatProcessor(QObject* parent)
: AudioProcessor(parent) : AudioProcessor(parent)
, m_tempo(new_aubio_tempo("default", 1024, ac::CHUNK_SIZE, ac::SAMPLE_RATE)) , m_tempo(new_aubio_tempo("default", 1024, ac::CHUNK_SIZE, ac::SAMPLE_RATE))
, m_in(new_fvec(ac::CHUNK_SIZE)) , m_in(new_fvec(ac::CHUNK_SIZE))
, m_out(new_fvec(2)) { , m_out(new_fvec(2)) {};
};
BeatProcessor::~BeatProcessor() { BeatProcessor::~BeatProcessor() {
if (m_tempo) { if (m_tempo) {
@@ -36,13 +35,15 @@ void BeatProcessor::process() {
} }
} }
BeatTracker::BeatTracker(QObject* parent) BeatTracker::BeatTracker(QObject* parent) : AudioProvider(parent), m_bpm(120) {
: AudioProvider(parent)
, m_bpm(120) {
m_processor = new BeatProcessor(); m_processor = new BeatProcessor();
init(); init();
connect(static_cast<BeatProcessor*>(m_processor), &BeatProcessor::beat, this, &BeatTracker::updateBpm); connect(
static_cast<BeatProcessor*>(m_processor),
&BeatProcessor::beat,
this,
&BeatTracker::updateBpm);
} }
smpl_t BeatTracker::bpm() const { smpl_t BeatTracker::bpm() const {
+24 -24
View File
@@ -7,43 +7,43 @@
namespace ZShell::services { namespace ZShell::services {
class BeatProcessor : public AudioProcessor { class BeatProcessor : public AudioProcessor {
Q_OBJECT Q_OBJECT
public: public:
explicit BeatProcessor(QObject* parent = nullptr); explicit BeatProcessor(QObject* parent = nullptr);
~BeatProcessor() override; ~BeatProcessor() override;
signals: signals:
void beat(smpl_t bpm); void beat(smpl_t bpm);
protected: protected:
void process() override; void process() override;
private: private:
aubio_tempo_t* m_tempo; aubio_tempo_t* m_tempo;
fvec_t* m_in; fvec_t* m_in;
fvec_t* m_out; fvec_t* m_out;
}; };
class BeatTracker : public AudioProvider { class BeatTracker : public AudioProvider {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(smpl_t bpm READ bpm NOTIFY bpmChanged) Q_PROPERTY(smpl_t bpm READ bpm NOTIFY bpmChanged)
public: public:
explicit BeatTracker(QObject* parent = nullptr); explicit BeatTracker(QObject* parent = nullptr);
[[nodiscard]] smpl_t bpm() const; [[nodiscard]] smpl_t bpm() const;
signals: signals:
void bpmChanged(); void bpmChanged();
void beat(smpl_t bpm); void beat(smpl_t bpm);
private: private:
smpl_t m_bpm; smpl_t m_bpm;
void updateBpm(smpl_t bpm); void updateBpm(smpl_t bpm);
}; };
} // namespace ZShell::services } // namespace ZShell::services
+18 -12
View File
@@ -2,7 +2,6 @@
#include "audiocollector.hpp" #include "audiocollector.hpp"
#include "audioprovider.hpp" #include "audioprovider.hpp"
#include <algorithm>
#include <cava/cavacore.h> #include <cava/cavacore.h>
#include <cstddef> #include <cstddef>
#include <qdebug.h> #include <qdebug.h>
@@ -14,8 +13,7 @@ CavaProcessor::CavaProcessor(QObject* parent)
, m_plan(nullptr) , m_plan(nullptr)
, m_in(new double[ac::CHUNK_SIZE]) , m_in(new double[ac::CHUNK_SIZE])
, m_out(nullptr) , m_out(nullptr)
, m_bars(0) { , m_bars(0) {};
};
CavaProcessor::~CavaProcessor() { CavaProcessor::~CavaProcessor() {
cleanup(); cleanup();
@@ -27,7 +25,8 @@ void CavaProcessor::process() {
return; return;
} }
const int count = static_cast<int>(AudioCollector::instance().readChunk(m_in)); const int count =
static_cast<int>(AudioCollector::instance().readChunk(m_in));
// Process in data via cava // Process in data via cava
cava_execute(m_in, count, m_out, m_plan); cava_execute(m_in, count, m_out, m_plan);
@@ -35,7 +34,7 @@ void CavaProcessor::process() {
// Apply monstercat filter // Apply monstercat filter
QVector<double> values(m_bars); QVector<double> values(m_bars);
for(int i = 0; i < m_bars; ++i) { for (int i = 0; i < m_bars; ++i) {
values[i] = m_out[i]; values[i] = m_out[i];
} }
@@ -63,7 +62,8 @@ void CavaProcessor::process() {
void CavaProcessor::setBars(int bars) { void CavaProcessor::setBars(int bars) {
if (bars < 0) { if (bars < 0) {
qWarning() << "CavaProcessor::setBars: bars must be greater than 0. Setting to 0."; qWarning() << "CavaProcessor::setBars: bars must be greater than 0. "
"Setting to 0.";
bars = 0; bars = 0;
} }
@@ -100,13 +100,15 @@ void CavaProcessor::initCava() {
} }
CavaProvider::CavaProvider(QObject* parent) CavaProvider::CavaProvider(QObject* parent)
: AudioProvider(parent) : AudioProvider(parent), m_bars(0), m_values(m_bars, 0.0) {
, m_bars(0)
, m_values(m_bars, 0.0) {
m_processor = new CavaProcessor(); m_processor = new CavaProcessor();
init(); init();
connect(static_cast<CavaProcessor*>(m_processor), &CavaProcessor::valuesChanged, this, &CavaProvider::updateValues); connect(
static_cast<CavaProcessor*>(m_processor),
&CavaProcessor::valuesChanged,
this,
&CavaProvider::updateValues);
} }
int CavaProvider::bars() const { int CavaProvider::bars() const {
@@ -115,7 +117,8 @@ int CavaProvider::bars() const {
void CavaProvider::setBars(int bars) { void CavaProvider::setBars(int bars) {
if (bars < 0) { if (bars < 0) {
qWarning() << "CavaProvider::setBars: bars must be greater than 0. Setting to 0."; qWarning() << "CavaProvider::setBars: bars must be greater than 0. "
"Setting to 0.";
bars = 0; bars = 0;
} }
@@ -129,7 +132,10 @@ void CavaProvider::setBars(int bars) {
emit valuesChanged(); emit valuesChanged();
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
static_cast<CavaProcessor*>(m_processor), &CavaProcessor::setBars, Qt::QueuedConnection, bars); static_cast<CavaProcessor*>(m_processor),
&CavaProcessor::setBars,
Qt::QueuedConnection,
bars);
} }
QVector<double> CavaProvider::values() const { QVector<double> CavaProvider::values() const {
+34 -34
View File
@@ -7,58 +7,58 @@
namespace ZShell::services { namespace ZShell::services {
class CavaProcessor : public AudioProcessor { class CavaProcessor : public AudioProcessor {
Q_OBJECT Q_OBJECT
public: public:
explicit CavaProcessor(QObject* parent = nullptr); explicit CavaProcessor(QObject* parent = nullptr);
~CavaProcessor() override; ~CavaProcessor() override;
void setBars(int bars); void setBars(int bars);
signals: signals:
void valuesChanged(QVector<double> values); void valuesChanged(QVector<double> values);
protected: protected:
void process() override; void process() override;
private: private:
struct cava_plan* m_plan; struct cava_plan* m_plan;
double* m_in; double* m_in;
double* m_out; double* m_out;
int m_bars; int m_bars;
QVector<double> m_values; QVector<double> m_values;
void reload(); void reload();
void initCava(); void initCava();
void cleanup(); void cleanup();
}; };
class CavaProvider : public AudioProvider { class CavaProvider : public AudioProvider {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(int bars READ bars WRITE setBars NOTIFY barsChanged) Q_PROPERTY(int bars READ bars WRITE setBars NOTIFY barsChanged)
Q_PROPERTY(QVector<double> values READ values NOTIFY valuesChanged) Q_PROPERTY(QVector<double> values READ values NOTIFY valuesChanged)
public: public:
explicit CavaProvider(QObject* parent = nullptr); explicit CavaProvider(QObject* parent = nullptr);
[[nodiscard]] int bars() const; [[nodiscard]] int bars() const;
void setBars(int bars); void setBars(int bars);
[[nodiscard]] QVector<double> values() const; [[nodiscard]] QVector<double> values() const;
signals: signals:
void barsChanged(); void barsChanged();
void valuesChanged(); void valuesChanged();
private: private:
int m_bars; int m_bars;
QVector<double> m_values; QVector<double> m_values;
void updateValues(QVector<double> values); void updateValues(QVector<double> values);
}; };
} // namespace ZShell::services } // namespace ZShell::services
+12 -7
View File
@@ -8,8 +8,7 @@
namespace ZShell::services { namespace ZShell::services {
Cpu::Cpu(QObject* parent) Cpu::Cpu(QObject* parent) : TickingService(parent) {
: TickingService(parent) {
readNameOnce(); readNameOnce();
} }
@@ -41,7 +40,8 @@ void Cpu::readNameOnce() {
const QByteArray data = f.readAll(); const QByteArray data = f.readAll();
f.close(); f.close();
static const QRegularExpression re(QStringLiteral("model name\\s*:\\s*(.+)")); static const QRegularExpression re(
QStringLiteral("model name\\s*:\\s*(.+)"));
const auto match = re.match(QString::fromLatin1(data)); const auto match = re.match(QString::fromLatin1(data));
if (!match.hasMatch()) { if (!match.hasMatch()) {
return; return;
@@ -64,8 +64,9 @@ void Cpu::refreshPercentage() {
const QByteArray data = f.readAll(); const QByteArray data = f.readAll();
f.close(); f.close();
static const QRegularExpression re( static const QRegularExpression re(QStringLiteral(
QStringLiteral("^cpu\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)")); "^cpu\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)"
"\\s+(\\d+)\\s+(\\d+)"));
const auto match = re.match(QString::fromLatin1(data)); const auto match = re.match(QString::fromLatin1(data));
if (!match.hasMatch()) { if (!match.hasMatch()) {
return; return;
@@ -83,7 +84,10 @@ void Cpu::refreshPercentage() {
const quint64 totalDiff = total > m_lastTotal ? total - m_lastTotal : 0; const quint64 totalDiff = total > m_lastTotal ? total - m_lastTotal : 0;
const quint64 idleDiff = idle > m_lastIdle ? idle - m_lastIdle : 0; const quint64 idleDiff = idle > m_lastIdle ? idle - m_lastIdle : 0;
const qreal newPerc = totalDiff > 0 ? 1.0 - static_cast<qreal>(idleDiff) / static_cast<qreal>(totalDiff) : 0.0; const qreal newPerc =
totalDiff > 0
? 1.0 - static_cast<qreal>(idleDiff) / static_cast<qreal>(totalDiff)
: 0.0;
m_lastTotal = total; m_lastTotal = total;
m_lastIdle = idle; m_lastIdle = idle;
@@ -105,7 +109,8 @@ void Cpu::refreshTemperature() {
QString Cpu::cleanName(QString s) { QString Cpu::cleanName(QString s) {
static const QRegularExpression noise( static const QRegularExpression noise(
QStringLiteral("\\(R\\)|\\(TM\\)|CPU|\\d+(?:th|nd|rd|st) Gen |Core |Processor"), QStringLiteral(
"\\(R\\)|\\(TM\\)|CPU|\\d+(?:th|nd|rd|st) Gen |Core |Processor"),
QRegularExpression::CaseInsensitiveOption); QRegularExpression::CaseInsensitiveOption);
static const QRegularExpression spaces(QStringLiteral("\\s+")); static const QRegularExpression spaces(QStringLiteral("\\s+"));
+28 -28
View File
@@ -7,42 +7,42 @@
namespace ZShell::services { namespace ZShell::services {
class Cpu : public TickingService { class Cpu : public TickingService {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_SINGLETON QML_SINGLETON
Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged) Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged)
Q_PROPERTY(qreal temperature READ temperature NOTIFY temperatureChanged) Q_PROPERTY(qreal temperature READ temperature NOTIFY temperatureChanged)
public: public:
explicit Cpu(QObject* parent = nullptr); explicit Cpu(QObject* parent = nullptr);
[[nodiscard]] QString name() const; [[nodiscard]] QString name() const;
[[nodiscard]] qreal percentage() const; [[nodiscard]] qreal percentage() const;
[[nodiscard]] qreal temperature() const; [[nodiscard]] qreal temperature() const;
signals: signals:
void nameChanged(); void nameChanged();
void percentageChanged(); void percentageChanged();
void temperatureChanged(); void temperatureChanged();
protected: protected:
void tick() override; void tick() override;
private: private:
void readNameOnce(); void readNameOnce();
void refreshPercentage(); void refreshPercentage();
void refreshTemperature(); void refreshTemperature();
[[nodiscard]] static QString cleanName(QString s); [[nodiscard]] static QString cleanName(QString s);
QString m_name; QString m_name;
qreal m_percentage = 0.0; qreal m_percentage = 0.0;
qreal m_temperature = 0.0; qreal m_temperature = 0.0;
quint64 m_lastIdle = 0; quint64 m_lastIdle = 0;
quint64 m_lastTotal = 0; quint64 m_lastTotal = 0;
bool m_nameLoaded = false; bool m_nameLoaded = false;
}; };
} // namespace ZShell::services } // namespace ZShell::services
+66 -35
View File
@@ -6,25 +6,31 @@
namespace ZShell::services { namespace ZShell::services {
DesktopModel::DesktopModel(QObject *parent) : QAbstractListModel(parent) { DesktopModel::DesktopModel(QObject* parent) : QAbstractListModel(parent) {}
}
int DesktopModel::rowCount(const QModelIndex &parent) const { int DesktopModel::rowCount(const QModelIndex& parent) const {
if (parent.isValid()) return 0; if (parent.isValid()) return 0;
return m_items.count(); return static_cast<int>(m_items.size());
} }
QVariant DesktopModel::data(const QModelIndex &index, int role) const { QVariant DesktopModel::data(const QModelIndex& index, int role) const {
if (!index.isValid() || index.row() >= m_items.size()) return QVariant(); if (!index.isValid() || index.row() >= static_cast<int>(m_items.size()))
return QVariant();
const DesktopItem &item = m_items[index.row()]; const DesktopItem& item = m_items[index.row()];
switch (role) { switch (role) {
case FileNameRole: return item.fileName; case FileNameRole:
case FilePathRole: return item.filePath; return item.fileName;
case IsDirRole: return item.isDir; case FilePathRole:
case GridXRole: return item.gridX; return item.filePath;
case GridYRole: return item.gridY; case IsDirRole:
default: return QVariant(); return item.isDir;
case GridXRole:
return item.gridX;
case GridYRole:
return item.gridY;
default:
return QVariant();
} }
} }
@@ -38,8 +44,8 @@ QHash<int, QByteArray> DesktopModel::roleNames() const {
return roles; return roles;
} }
QPoint DesktopModel::getEmptySpot(const QSet<QString> &occupied) const { QPoint DesktopModel::getEmptySpot(const QSet<QString>& occupied) const {
for (int x = 0; ; ++x) { for (int x = 0;; ++x) {
for (int y = 0; y < m_rows; ++y) { for (int y = 0; y < m_rows; ++y) {
QString key = QString::number(x) + "," + QString::number(y); QString key = QString::number(x) + "," + QString::number(y);
if (!occupied.contains(key)) { if (!occupied.contains(key)) {
@@ -49,7 +55,7 @@ QPoint DesktopModel::getEmptySpot(const QSet<QString> &occupied) const {
} }
} }
void DesktopModel::loadDirectory(const QString &path) { void DesktopModel::loadDirectory(const QString& path) {
m_watchedPath = path; m_watchedPath = path;
if (!m_watcher.directories().isEmpty()) if (!m_watcher.directories().isEmpty())
@@ -57,9 +63,12 @@ void DesktopModel::loadDirectory(const QString &path) {
m_watcher.addPath(path); m_watcher.addPath(path);
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, connect(
this, &DesktopModel::onDirectoryChanged, &m_watcher,
Qt::UniqueConnection); &QFileSystemWatcher::directoryChanged,
this,
&DesktopModel::onDirectoryChanged,
Qt::UniqueConnection);
beginResetModel(); beginResetModel();
m_items.clear(); m_items.clear();
@@ -72,14 +81,16 @@ void DesktopModel::loadDirectory(const QString &path) {
QVariantMap savedLayout = sm.getLayout(); QVariantMap savedLayout = sm.getLayout();
QSet<QString> occupied; QSet<QString> occupied;
for (const QFileInfo &fileInfo : list) { for (const QFileInfo& fileInfo : list) {
if (savedLayout.contains(fileInfo.fileName())) { if (savedLayout.contains(fileInfo.fileName())) {
QVariantMap pos = savedLayout[fileInfo.fileName()].toMap(); QVariantMap pos = savedLayout[fileInfo.fileName()].toMap();
occupied.insert(QString::number(pos["x"].toInt()) + "," + QString::number(pos["y"].toInt())); occupied.insert(
QString::number(pos["x"].toInt()) + "," +
QString::number(pos["y"].toInt()));
} }
} }
for (const QFileInfo &fileInfo : list) { for (const QFileInfo& fileInfo : list) {
DesktopItem item; DesktopItem item;
item.fileName = fileInfo.fileName(); item.fileName = fileInfo.fileName();
item.filePath = fileInfo.absoluteFilePath(); item.filePath = fileInfo.absoluteFilePath();
@@ -93,7 +104,9 @@ void DesktopModel::loadDirectory(const QString &path) {
QPoint spot = getEmptySpot(occupied); QPoint spot = getEmptySpot(occupied);
item.gridX = spot.x(); item.gridX = spot.x();
item.gridY = spot.y(); item.gridY = spot.y();
occupied.insert(QString::number(item.gridX) + "," + QString::number(item.gridY)); occupied.insert(
QString::number(item.gridX) + "," +
QString::number(item.gridY));
} }
m_items.append(item); m_items.append(item);
} }
@@ -105,7 +118,7 @@ void DesktopModel::onDirectoryChanged() {
} }
void DesktopModel::moveIcon(int index, int newX, int newY) { void DesktopModel::moveIcon(int index, int newX, int newY) {
if (index < 0 || index >= m_items.size()) return; if (index < 0 || index >= static_cast<int>(m_items.size())) return;
m_items[index].gridX = newX; m_items[index].gridX = newX;
m_items[index].gridY = newY; m_items[index].gridY = newY;
@@ -129,7 +142,13 @@ void DesktopModel::saveCurrentLayout() {
sm.saveLayout(layout); sm.saveLayout(layout);
} }
void DesktopModel::massMove(const QVariantList& selectedPathsList, const QString& leaderPath, int targetX, int targetY, int maxCol, int maxRow) { void DesktopModel::massMove(
const QVariantList& selectedPathsList,
const QString& leaderPath,
int targetX,
int targetY,
int maxCol,
int maxRow) {
QStringList selectedPaths; QStringList selectedPaths;
for (const QVariant& v : selectedPathsList) { for (const QVariant& v : selectedPathsList) {
selectedPaths << v.toString(); selectedPaths << v.toString();
@@ -151,12 +170,13 @@ void DesktopModel::massMove(const QVariantList& selectedPathsList, const QString
if (deltaX == 0 && deltaY == 0) return; if (deltaX == 0 && deltaY == 0) return;
if (selectedPaths.size() == 1 && targetX >= 0 && targetX <= maxCol && targetY >= 0 && targetY <= maxRow) { if (selectedPaths.size() == 1 && targetX >= 0 && targetX <= maxCol &&
targetY >= 0 && targetY <= maxRow) {
QString movingPath = selectedPaths.first(); QString movingPath = selectedPaths.first();
int movingIndex = -1; int movingIndex = -1;
int targetIndex = -1; int targetIndex = -1;
for (int i = 0; i < m_items.size(); ++i) { for (int i = 0; i < static_cast<int>(m_items.size()); ++i) {
if (m_items[i].filePath == movingPath) { if (m_items[i].filePath == movingPath) {
movingIndex = i; movingIndex = i;
} else if (m_items[i].gridX == targetX && m_items[i].gridY == targetY) { } else if (m_items[i].gridX == targetX && m_items[i].gridY == targetY) {
@@ -170,7 +190,10 @@ void DesktopModel::massMove(const QVariantList& selectedPathsList, const QString
m_items[movingIndex].gridX = targetX; m_items[movingIndex].gridX = targetX;
m_items[movingIndex].gridY = targetY; m_items[movingIndex].gridY = targetY;
emit dataChanged(index(0, 0), index(m_items.size() - 1, 0), {GridXRole, GridYRole}); emit dataChanged(
index(0, 0),
index(static_cast<int>(m_items.size()) - 1, 0),
{GridXRole, GridYRole});
saveCurrentLayout(); saveCurrentLayout();
return; return;
} }
@@ -179,11 +202,13 @@ void DesktopModel::massMove(const QVariantList& selectedPathsList, const QString
QList<DesktopItem*> movingItems; QList<DesktopItem*> movingItems;
QSet<QString> occupied; QSet<QString> occupied;
for (int i = 0; i < m_items.size(); ++i) { for (int i = 0; i < static_cast<int>(m_items.size()); ++i) {
if (selectedPaths.contains(m_items[i].filePath)) { if (selectedPaths.contains(m_items[i].filePath)) {
movingItems.append(&m_items[i]); movingItems.append(&m_items[i]);
} else { } else {
occupied.insert(QString::number(m_items[i].gridX) + "," + QString::number(m_items[i].gridY)); occupied.insert(
QString::number(m_items[i].gridX) + "," +
QString::number(m_items[i].gridY));
} }
} }
@@ -191,8 +216,10 @@ void DesktopModel::massMove(const QVariantList& selectedPathsList, const QString
int newX = item->gridX + deltaX; int newX = item->gridX + deltaX;
int newY = item->gridY + deltaY; int newY = item->gridY + deltaY;
bool outOfBounds = newX < 0 || newX > maxCol || newY < 0 || newY > maxRow; bool outOfBounds = newX < 0 || newX > maxCol || newY < 0 ||
bool collision = occupied.contains(QString::number(newX) + "," + QString::number(newY)); newY > maxRow;
bool collision = occupied.contains(
QString::number(newX) + "," + QString::number(newY));
if (outOfBounds || collision) { if (outOfBounds || collision) {
bool found = false; bool found = false;
@@ -208,15 +235,19 @@ void DesktopModel::massMove(const QVariantList& selectedPathsList, const QString
} }
} }
} else { } else {
occupied.insert(QString::number(newX) + "," + QString::number(newY)); occupied.insert(
QString::number(newX) + "," + QString::number(newY));
} }
item->gridX = newX; item->gridX = newX;
item->gridY = newY; item->gridY = newY;
} }
emit dataChanged(index(0, 0), index(m_items.size() - 1, 0), {GridXRole, GridYRole}); emit dataChanged(
index(0, 0),
index(static_cast<int>(m_items.size()) - 1, 0),
{GridXRole, GridYRole});
saveCurrentLayout(); saveCurrentLayout();
} }
}; }; // namespace ZShell::services
+51 -43
View File
@@ -17,49 +17,57 @@ struct DesktopItem {
}; };
class DesktopModel : public QAbstractListModel { class DesktopModel : public QAbstractListModel {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
public: public:
enum DesktopRoles { enum DesktopRoles {
FileNameRole = Qt::UserRole + 1, FileNameRole = Qt::UserRole + 1,
FilePathRole, FilePathRole,
IsDirRole, IsDirRole,
GridXRole, GridXRole,
GridYRole GridYRole
};
explicit DesktopModel(QObject* parent = nullptr);
[[nodiscard]] int rowCount(
const QModelIndex& parent = QModelIndex()) const override;
[[nodiscard]] QVariant data(
const QModelIndex& index, int role = Qt::DisplayRole) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void loadDirectory(const QString& path);
Q_INVOKABLE void moveIcon(int index, int newX, int newY);
Q_INVOKABLE void massMove(
const QVariantList& selectedPathsList,
const QString& leaderPath,
int targetX,
int targetY,
int maxCol,
int maxRow);
Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged)
[[nodiscard]] int rows() const { return m_rows; }
void setRows(int r) {
if (m_rows != r) {
m_rows = r;
emit rowsChanged();
}
}
signals:
void rowsChanged();
private:
int m_rows = 1;
QList<DesktopItem> m_items;
QString m_watchedPath;
QFileSystemWatcher m_watcher;
void saveCurrentLayout();
[[nodiscard]] QPoint getEmptySpot(const QSet<QString>& occupied) const;
void onDirectoryChanged();
}; };
explicit DesktopModel(QObject *parent = nullptr); }; // namespace ZShell::services
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void loadDirectory(const QString &path);
Q_INVOKABLE void moveIcon(int index, int newX, int newY);
Q_INVOKABLE void massMove(const QVariantList &selectedPathsList, const QString &leaderPath, int targetX, int targetY, int maxCol, int maxRow);
Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged)
public:
[[nodiscard]] int rows() const {
return m_rows;
}
void setRows(int r) {
if (m_rows != r) { m_rows = r; emit rowsChanged(); }
}
signals:
void rowsChanged();
private:
int m_rows = 1;
QList<DesktopItem> m_items;
QString m_watchedPath;
QFileSystemWatcher m_watcher;
void saveCurrentLayout();
[[nodiscard]] QPoint getEmptySpot(const QSet<QString> &occupied) const;
void onDirectoryChanged();
};
};
@@ -9,11 +9,12 @@
namespace ZShell::services { namespace ZShell::services {
DesktopStateManager::DesktopStateManager(QObject *parent) : QObject(parent) { DesktopStateManager::DesktopStateManager(QObject* parent) : QObject(parent) {}
}
QString DesktopStateManager::getConfigFilePath() const { QString DesktopStateManager::getConfigFilePath() const {
QString configDir = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/zshell"; QString configDir =
QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) +
"/zshell";
QDir dir(configDir); QDir dir(configDir);
if (!dir.exists()) { if (!dir.exists()) {
dir.mkpath("."); dir.mkpath(".");
@@ -30,7 +31,8 @@ void DesktopStateManager::saveLayout(const QVariantMap& layout) {
file.write(doc.toJson(QJsonDocument::Indented)); file.write(doc.toJson(QJsonDocument::Indented));
file.close(); file.close();
} else { } else {
qWarning() << "zshell: Cannot save desktop layout to" << getConfigFilePath(); qWarning() << "zshell: Cannot save desktop layout to"
<< getConfigFilePath();
} }
} }
@@ -7,18 +7,18 @@
namespace ZShell::services { namespace ZShell::services {
class DesktopStateManager : public QObject { class DesktopStateManager : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_SINGLETON QML_SINGLETON
public: public:
explicit DesktopStateManager(QObject *parent = nullptr); explicit DesktopStateManager(QObject* parent = nullptr);
Q_INVOKABLE void saveLayout(const QVariantMap& layout); Q_INVOKABLE void saveLayout(const QVariantMap& layout);
Q_INVOKABLE QVariantMap getLayout(); Q_INVOKABLE QVariantMap getLayout();
private: private:
[[nodiscard]] QString getConfigFilePath() const; [[nodiscard]] QString getConfigFilePath() const;
}; };
} // namespace ZShell::services } // namespace ZShell::services
+12 -5
View File
@@ -8,13 +8,17 @@ constexpr qreal kKib = 1024.0;
} // namespace } // namespace
DiskInfo::DiskInfo(QString mount, quint64 usedBytes, quint64 totalBytes, bool hasRoot, QObject* parent) DiskInfo::DiskInfo(
QString mount,
quint64 usedBytes,
quint64 totalBytes,
bool hasRoot,
QObject* parent)
: QObject(parent) : QObject(parent)
, m_mount(std::move(mount)) , m_mount(std::move(mount))
, m_usedBytes(usedBytes) , m_usedBytes(usedBytes)
, m_totalBytes(totalBytes) , m_totalBytes(totalBytes)
, m_hasRoot(hasRoot) { , m_hasRoot(hasRoot) {}
}
QString DiskInfo::mount() const { QString DiskInfo::mount() const {
return m_mount; return m_mount;
@@ -29,12 +33,15 @@ qreal DiskInfo::total() const {
} }
qreal DiskInfo::free() const { qreal DiskInfo::free() const {
const quint64 freeBytes = m_totalBytes > m_usedBytes ? m_totalBytes - m_usedBytes : 0; const quint64 freeBytes =
m_totalBytes > m_usedBytes ? m_totalBytes - m_usedBytes : 0;
return static_cast<qreal>(freeBytes) / kKib; return static_cast<qreal>(freeBytes) / kKib;
} }
qreal DiskInfo::perc() const { qreal DiskInfo::perc() const {
return m_totalBytes > 0 ? static_cast<qreal>(m_usedBytes) / static_cast<qreal>(m_totalBytes) : 0.0; return m_totalBytes > 0 ? static_cast<qreal>(m_usedBytes) /
static_cast<qreal>(m_totalBytes)
: 0.0;
} }
bool DiskInfo::hasRoot() const { bool DiskInfo::hasRoot() const {
+34 -29
View File
@@ -6,41 +6,46 @@
namespace ZShell::services { namespace ZShell::services {
class DiskInfo : public QObject { class DiskInfo : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("DiskInfo is created by DiskUsage") QML_UNCREATABLE("DiskInfo is created by DiskUsage")
Q_PROPERTY(QString mount READ mount CONSTANT) Q_PROPERTY(QString mount READ mount CONSTANT)
Q_PROPERTY(qreal used READ used NOTIFY usedChanged) Q_PROPERTY(qreal used READ used NOTIFY usedChanged)
Q_PROPERTY(qreal total READ total NOTIFY totalChanged) Q_PROPERTY(qreal total READ total NOTIFY totalChanged)
Q_PROPERTY(qreal free READ free NOTIFY freeChanged) Q_PROPERTY(qreal free READ free NOTIFY freeChanged)
Q_PROPERTY(qreal perc READ perc NOTIFY percChanged) Q_PROPERTY(qreal perc READ perc NOTIFY percChanged)
Q_PROPERTY(bool hasRoot READ hasRoot NOTIFY hasRootChanged) Q_PROPERTY(bool hasRoot READ hasRoot NOTIFY hasRootChanged)
public: public:
DiskInfo(QString mount, quint64 usedBytes, quint64 totalBytes, bool hasRoot, QObject* parent = nullptr); DiskInfo(
QString mount,
quint64 usedBytes,
quint64 totalBytes,
bool hasRoot,
QObject* parent = nullptr);
[[nodiscard]] QString mount() const; [[nodiscard]] QString mount() const;
[[nodiscard]] qreal used() const; [[nodiscard]] qreal used() const;
[[nodiscard]] qreal total() const; [[nodiscard]] qreal total() const;
[[nodiscard]] qreal free() const; [[nodiscard]] qreal free() const;
[[nodiscard]] qreal perc() const; [[nodiscard]] qreal perc() const;
[[nodiscard]] bool hasRoot() const; [[nodiscard]] bool hasRoot() const;
void update(quint64 usedBytes, quint64 totalBytes, bool hasRoot); void update(quint64 usedBytes, quint64 totalBytes, bool hasRoot);
signals: signals:
void usedChanged(); void usedChanged();
void totalChanged(); void totalChanged();
void freeChanged(); void freeChanged();
void percChanged(); void percChanged();
void hasRootChanged(); void hasRootChanged();
private: private:
QString m_mount; QString m_mount;
quint64 m_usedBytes; quint64 m_usedBytes;
quint64 m_totalBytes; quint64 m_totalBytes;
bool m_hasRoot; bool m_hasRoot;
}; };
} // namespace ZShell::services } // namespace ZShell::services
+118 -77
View File
@@ -17,26 +17,31 @@ namespace ZShell::services {
namespace { namespace {
constexpr const char* kTypeDetectScript = constexpr const char* kTypeDetectScript =
"if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null 2>&1; then echo NVIDIA;" "if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null "
" elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC;" "2>&1; then echo NVIDIA;"
" elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep "
"-q .; then echo GENERIC;"
" else echo NONE; fi"; " else echo NONE; fi";
constexpr const char* kNameDetectScript = "nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null" constexpr const char* kNameDetectScript =
" || glxinfo -B 2>/dev/null | grep 'Device:' | cut -d':' -f2 | cut -d'(' -f1" "nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null"
" || lspci 2>/dev/null | grep -i 'vga\\|3d controller\\|display' | head -1"; " || glxinfo -B 2>/dev/null | grep 'Device:' | cut -d':' -f2 | cut -d'(' "
"-f1"
" || lspci 2>/dev/null | grep -i 'vga\\|3d controller\\|display' | head -1";
} // namespace } // namespace
Gpu::Gpu(QObject* parent) Gpu::Gpu(QObject* parent) : TickingService(parent) {
: TickingService(parent) { QString configPath =
QString configPath = QDir::homePath() + QStringLiteral("/.config/zshell/config.json"); QDir::homePath() + QStringLiteral("/.config/zshell/config.json");
auto reloadConfig = [this, configPath]() { auto reloadConfig = [this, configPath]() {
QFile file(configPath); QFile file(configPath);
if (file.open(QIODevice::ReadOnly)) { if (file.open(QIODevice::ReadOnly)) {
QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
if (!doc.isNull()) { if (!doc.isNull()) {
QJsonObject services = doc.object().value("services").toObject(); QJsonObject services =
doc.object().value("services").toObject();
setUserType(parseType(services.value("gpuType").toString())); setUserType(parseType(services.value("gpuType").toString()));
} }
} }
@@ -46,18 +51,25 @@ Gpu::Gpu(QObject* parent)
static QFileSystemWatcher* watcher = new QFileSystemWatcher(); static QFileSystemWatcher* watcher = new QFileSystemWatcher();
if (!watcher->files().contains(configPath)) { if (!watcher->files().contains(configPath)) {
QObject::connect(watcher, &QFileSystemWatcher::fileChanged, this, [this, configPath]() { QObject::connect(
QTimer::singleShot(100, this, [this, configPath]() { watcher,
QFile file(configPath); &QFileSystemWatcher::fileChanged,
if (file.exists()) { this,
QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); [this, configPath]() {
if (!doc.isNull()) { QTimer::singleShot(100, this, [this, configPath]() {
QJsonObject services = doc.object().value("services").toObject(); QFile file(configPath);
setUserType(parseType(services.value("gpuType").toString())); if (file.exists()) {
QJsonDocument doc =
QJsonDocument::fromJson(file.readAll());
if (!doc.isNull()) {
QJsonObject services =
doc.object().value("services").toObject();
setUserType(parseType(
services.value("gpuType").toString()));
}
} }
} });
}); });
});
watcher->addPath(configPath); watcher->addPath(configPath);
} }
@@ -134,13 +146,13 @@ void Gpu::setName(QString value) {
} }
void Gpu::setMemoryUsed(qreal value) { void Gpu::setMemoryUsed(qreal value) {
if (m_memoryUsed == value) return; if (std::abs(m_memoryUsed - value) < 0.001) return;
m_memoryUsed = value; m_memoryUsed = value;
Q_EMIT memoryUsedChanged(); Q_EMIT memoryUsedChanged();
} }
void Gpu::setMemoryTotal(qreal value) { void Gpu::setMemoryTotal(qreal value) {
if (m_memoryTotal == value) return; if (std::abs(m_memoryTotal - value) < 0.001) return;
m_memoryTotal = value; m_memoryTotal = value;
Q_EMIT memoryTotalChanged(); Q_EMIT memoryTotalChanged();
} }
@@ -169,15 +181,22 @@ void Gpu::detectTypeOnce() {
return; return;
} }
m_typeProc = new QProcess(this); m_typeProc = new QProcess(this);
QObject::connect(m_typeProc, &QProcess::finished, this, [this](int, QProcess::ExitStatus) { QObject::connect(
const QByteArray out = m_typeProc->readAllStandardOutput().trimmed(); m_typeProc,
&QProcess::finished,
this,
[this](int, QProcess::ExitStatus) {
const QByteArray out =
m_typeProc->readAllStandardOutput().trimmed();
if (!out.isEmpty()) { if (!out.isEmpty()) {
setAutoType(parseType(QString::fromLatin1(out))); setAutoType(parseType(QString::fromLatin1(out)));
} }
m_typeProc->deleteLater(); m_typeProc->deleteLater();
m_typeProc = nullptr; m_typeProc = nullptr;
}); });
m_typeProc->start(QStringLiteral("sh"), { QStringLiteral("-c"), QString::fromLatin1(kTypeDetectScript) }); m_typeProc->start(
QStringLiteral("sh"),
{QStringLiteral("-c"), QString::fromLatin1(kTypeDetectScript)});
} }
void Gpu::detectNameOnce() { void Gpu::detectNameOnce() {
@@ -185,21 +204,30 @@ void Gpu::detectNameOnce() {
return; return;
} }
m_nameProc = new QProcess(this); m_nameProc = new QProcess(this);
QObject::connect(m_nameProc, &QProcess::finished, this, [this](int, QProcess::ExitStatus) { QObject::connect(
const QString output = QString::fromUtf8(m_nameProc->readAllStandardOutput()).trimmed(); m_nameProc,
&QProcess::finished,
this,
[this](int, QProcess::ExitStatus) {
const QString output =
QString::fromUtf8(m_nameProc->readAllStandardOutput()).trimmed();
if (!output.isEmpty()) { if (!output.isEmpty()) {
const QString lower = output.toLower(); const QString lower = output.toLower();
if (lower.contains(QStringLiteral("nvidia")) || lower.contains(QStringLiteral("geforce")) || if (lower.contains(QStringLiteral("nvidia")) ||
lower.contains(QStringLiteral("rtx")) || lower.contains(QStringLiteral("gtx")) || lower.contains(QStringLiteral("geforce")) ||
lower.contains(QStringLiteral("rx"))) { lower.contains(QStringLiteral("rtx")) ||
lower.contains(QStringLiteral("gtx")) ||
lower.contains(QStringLiteral("rx"))) {
setName(cleanName(output)); setName(cleanName(output));
} else { } else {
static const QRegularExpression bracketRe(QStringLiteral("\\[([^\\]]+)\\][^\\[]*$")); static const QRegularExpression bracketRe(
QStringLiteral("\\[([^\\]]+)\\][^\\[]*$"));
const auto bracket = bracketRe.match(output); const auto bracket = bracketRe.match(output);
if (bracket.hasMatch()) { if (bracket.hasMatch()) {
setName(cleanName(bracket.captured(1))); setName(cleanName(bracket.captured(1)));
} else { } else {
static const QRegularExpression colonRe(QStringLiteral(":\\s*(.+)")); static const QRegularExpression colonRe(
QStringLiteral(":\\s*(.+)"));
const auto colon = colonRe.match(output); const auto colon = colonRe.match(output);
if (colon.hasMatch()) { if (colon.hasMatch()) {
setName(cleanName(colon.captured(1))); setName(cleanName(colon.captured(1)));
@@ -210,17 +238,21 @@ void Gpu::detectNameOnce() {
m_nameProc->deleteLater(); m_nameProc->deleteLater();
m_nameProc = nullptr; m_nameProc = nullptr;
}); });
m_nameProc->start(QStringLiteral("sh"), { QStringLiteral("-c"), QString::fromLatin1(kNameDetectScript) }); m_nameProc->start(
QStringLiteral("sh"),
{QStringLiteral("-c"), QString::fromLatin1(kNameDetectScript)});
} }
void Gpu::readGenericUsage() { void Gpu::readGenericUsage() {
const QStringList paths = const QStringList paths = QDir(QStringLiteral("/sys/class/drm"))
QDir(QStringLiteral("/sys/class/drm")) .entryList(
.entryList(QStringList() << QStringLiteral("card*"), QDir::Dirs | QDir::NoDotAndDotDot); QStringList() << QStringLiteral("card*"),
QDir::Dirs | QDir::NoDotAndDotDot);
qreal sum = 0.0; qreal sum = 0.0;
int count = 0; int count = 0;
for (const QString& card : paths) { for (const QString& card : paths) {
QFile f(QStringLiteral("/sys/class/drm/%1/device/gpu_busy_percent").arg(card)); QFile f(QStringLiteral("/sys/class/drm/%1/device/gpu_busy_percent")
.arg(card));
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
continue; continue;
} }
@@ -244,57 +276,65 @@ void Gpu::startNvidiaUsage() {
return; return;
} }
m_nvidiaProc = new QProcess(this); m_nvidiaProc = new QProcess(this);
QObject::connect(m_nvidiaProc, &QProcess::readyReadStandardOutput, this, [this]() { QObject::connect(
while (m_nvidiaProc->canReadLine()) { m_nvidiaProc, &QProcess::readyReadStandardOutput, this, [this]() {
const QByteArray out = m_nvidiaProc->readLine(); while (m_nvidiaProc->canReadLine()) {
const QString output = QString::fromUtf8(out).trimmed(); const QByteArray out = m_nvidiaProc->readLine();
if (output.isEmpty()) const QString output = QString::fromUtf8(out).trimmed();
continue; if (output.isEmpty()) continue;
const QList<QString> parts = output.split(','); const QList<QString> parts = output.split(',');
if (parts.size() < 4) if (parts.size() < 4) return;
return;
bool ok1 = false; bool ok1 = false;
bool ok2 = false; bool ok2 = false;
bool ok3 = false; bool ok3 = false;
bool ok4 = false; bool ok4 = false;
const qreal usage = parts.at(0).trimmed().toDouble(&ok1) / 100.0; const qreal usage =
const qreal temp = parts.at(1).trimmed().toDouble(&ok2); parts.at(0).trimmed().toDouble(&ok1) / 100.0;
const qreal memUsed = parts.at(2).trimmed().toDouble(&ok3); const qreal temp = parts.at(1).trimmed().toDouble(&ok2);
const qreal memTotal = parts.at(3).trimmed().toDouble(&ok4); const qreal memUsed = parts.at(2).trimmed().toDouble(&ok3);
const qreal memTotal = parts.at(3).trimmed().toDouble(&ok4);
if (ok1 && std::abs(usage - m_percentage) > 0.0001) { if (ok1 && std::abs(usage - m_percentage) > 0.0001) {
m_percentage = usage; m_percentage = usage;
Q_EMIT percentageChanged(); Q_EMIT percentageChanged();
}
if (ok2 && std::abs(temp - m_temperature) > 0.05) {
m_temperature = temp;
Q_EMIT temperatureChanged();
}
if (ok3) {
setMemoryUsed(memUsed);
}
if (ok4) {
setMemoryTotal(memTotal);
}
} }
if (ok2 && std::abs(temp - m_temperature) > 0.05) { });
m_temperature = temp;
Q_EMIT temperatureChanged();
}
if (ok3) {
setMemoryUsed(memUsed);
}
if (ok4) {
setMemoryTotal(memTotal);
}
}
});
QObject::connect(m_nvidiaProc, &QProcess::finished, this, [this]() { QObject::connect(m_nvidiaProc, &QProcess::finished, this, [this]() {
m_nvidiaProc->deleteLater(); m_nvidiaProc->deleteLater();
m_nvidiaProc = nullptr; m_nvidiaProc = nullptr;
}); });
QObject::connect(m_nvidiaProc, &QProcess::errorOccurred, this, [this](QProcess::ProcessError) { QObject::connect(
m_nvidiaProc->deleteLater(); m_nvidiaProc,
m_nvidiaProc = nullptr; &QProcess::errorOccurred,
}); this,
[this](QProcess::ProcessError) {
m_nvidiaProc->deleteLater();
m_nvidiaProc = nullptr;
});
m_nvidiaProc->start(QStringLiteral("nvidia-smi"), { QStringLiteral("--query-gpu=utilization.gpu,temperature.gpu,memory.used,memory.total"), m_nvidiaProc->start(
QStringLiteral("--format=csv,noheader,nounits"), QStringLiteral("nvidia-smi"),
QStringLiteral("-lms"), {QStringLiteral(
QString::number(updateInterval()) }); "--query-gpu=utilization.gpu,temperature.gpu,"
"memory.used,memory.total"),
QStringLiteral("--format=csv,noheader,nounits"),
QStringLiteral("-lms"),
QString::number(updateInterval())});
} }
void Gpu::readGpuTemperature() { void Gpu::readGpuTemperature() {
@@ -322,7 +362,8 @@ Gpu::Type Gpu::parseType(const QString& s) {
QString Gpu::cleanName(QString s) { QString Gpu::cleanName(QString s) {
static const QRegularExpression noise( static const QRegularExpression noise(
QStringLiteral("\\(R\\)|\\(TM\\)|Graphics"), QRegularExpression::CaseInsensitiveOption); QStringLiteral("\\(R\\)|\\(TM\\)|Graphics"),
QRegularExpression::CaseInsensitiveOption);
static const QRegularExpression spaces(QStringLiteral("\\s+")); static const QRegularExpression spaces(QStringLiteral("\\s+"));
s.replace(noise, QString()); s.replace(noise, QString());
s.replace(spaces, QStringLiteral(" ")); s.replace(spaces, QStringLiteral(" "));
+64 -64
View File
@@ -8,81 +8,81 @@
namespace ZShell::services { namespace ZShell::services {
class Gpu : public TickingService { class Gpu : public TickingService {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_SINGLETON QML_SINGLETON
public: public:
enum Type { enum Type {
Auto, // user override is empty (config "") — defer to detected autoType Auto, // user override is empty (config "") — defer to detected autoType
None, // no usable GPU None, // no usable GPU
Nvidia, // queried via nvidia-smi Nvidia, // queried via nvidia-smi
Generic, // queried via /sys/class/drm/card*/device/gpu_busy_percent Generic, // queried via /sys/class/drm/card*/device/gpu_busy_percent
}; };
Q_ENUM(Type) Q_ENUM(Type)
private: private:
Q_PROPERTY(Type type READ type NOTIFY typeChanged) Q_PROPERTY(Type type READ type NOTIFY typeChanged)
Q_PROPERTY(Type userType READ userType NOTIFY userTypeChanged) Q_PROPERTY(Type userType READ userType NOTIFY userTypeChanged)
Q_PROPERTY(Type autoType READ autoType NOTIFY autoTypeChanged) Q_PROPERTY(Type autoType READ autoType NOTIFY autoTypeChanged)
Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged) Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged)
Q_PROPERTY(qreal temperature READ temperature NOTIFY temperatureChanged) Q_PROPERTY(qreal temperature READ temperature NOTIFY temperatureChanged)
Q_PROPERTY(qreal memoryUsed READ memoryUsed NOTIFY memoryUsedChanged) Q_PROPERTY(qreal memoryUsed READ memoryUsed NOTIFY memoryUsedChanged)
Q_PROPERTY(qreal memoryTotal READ memoryTotal NOTIFY memoryTotalChanged) Q_PROPERTY(qreal memoryTotal READ memoryTotal NOTIFY memoryTotalChanged)
public: public:
explicit Gpu(QObject* parent = nullptr); explicit Gpu(QObject* parent = nullptr);
[[nodiscard]] Type type() const; [[nodiscard]] Type type() const;
[[nodiscard]] Type userType() const; [[nodiscard]] Type userType() const;
[[nodiscard]] Type autoType() const; [[nodiscard]] Type autoType() const;
[[nodiscard]] QString name() const; [[nodiscard]] QString name() const;
[[nodiscard]] qreal percentage() const; [[nodiscard]] qreal percentage() const;
[[nodiscard]] qreal temperature() const; [[nodiscard]] qreal temperature() const;
[[nodiscard]] qreal memoryUsed() const; [[nodiscard]] qreal memoryUsed() const;
[[nodiscard]] qreal memoryTotal() const; [[nodiscard]] qreal memoryTotal() const;
signals: signals:
void typeChanged(); void typeChanged();
void userTypeChanged(); void userTypeChanged();
void autoTypeChanged(); void autoTypeChanged();
void nameChanged(); void nameChanged();
void percentageChanged(); void percentageChanged();
void temperatureChanged(); void temperatureChanged();
void memoryUsedChanged(); void memoryUsedChanged();
void memoryTotalChanged(); void memoryTotalChanged();
protected: protected:
void tick() override; void tick() override;
private: private:
void detectTypeOnce(); void detectTypeOnce();
void detectNameOnce(); void detectNameOnce();
void readGenericUsage(); void readGenericUsage();
void startNvidiaUsage(); void startNvidiaUsage();
void readGpuTemperature(); void readGpuTemperature();
void setUserType(Type value); void setUserType(Type value);
void setAutoType(Type value); void setAutoType(Type value);
void setName(QString value); void setName(QString value);
void setMemoryUsed(qreal value); void setMemoryUsed(qreal value);
void setMemoryTotal(qreal value); void setMemoryTotal(qreal value);
[[nodiscard]] static Type parseType(const QString& s); [[nodiscard]] static Type parseType(const QString& s);
[[nodiscard]] static QString cleanName(QString s); [[nodiscard]] static QString cleanName(QString s);
Type m_userType = Auto; Type m_userType = Auto;
Type m_autoType = None; Type m_autoType = None;
QString m_name; QString m_name;
qreal m_percentage = 0.0; qreal m_percentage = 0.0;
qreal m_temperature = 0.0; qreal m_temperature = 0.0;
qreal m_memoryUsed = 0.0; qreal m_memoryUsed = 0.0;
qreal m_memoryTotal = 0.0; qreal m_memoryTotal = 0.0;
QProcess* m_typeProc = nullptr; QProcess* m_typeProc = nullptr;
QProcess* m_nameProc = nullptr; QProcess* m_nameProc = nullptr;
QProcess* m_nvidiaProc = nullptr; QProcess* m_nvidiaProc = nullptr;
}; };
} // namespace ZShell::services } // namespace ZShell::services
+19 -26
View File
@@ -9,14 +9,14 @@ namespace ZShell::services {
HyprsunsetManager::HyprsunsetManager(QObject* parent) : QObject(parent) { HyprsunsetManager::HyprsunsetManager(QObject* parent) : QObject(parent) {
connect(&m_timer, &QTimer::timeout, this, &HyprsunsetManager::apply); connect(&m_timer, &QTimer::timeout, this, &HyprsunsetManager::apply);
connect(&m_manualTimer, &QTimer::timeout, this, [this] { connect(&m_manualTimer, &QTimer::timeout, this, [this] {
m_manualToggle = false; m_manualToggle = false;
emit manualToggleChanged(); emit manualToggleChanged();
apply(); apply();
}); });
connect(&m_startCooldown, &QTimer::timeout, this, [this] { connect(&m_startCooldown, &QTimer::timeout, this, [this] {
m_startAllowed = true; m_startAllowed = true;
apply(); apply();
}); });
m_startCooldown.start(2000); m_startCooldown.start(2000);
m_manualTimer.setSingleShot(true); m_manualTimer.setSingleShot(true);
@@ -51,16 +51,14 @@ int HyprsunsetManager::temp() const {
} }
void HyprsunsetManager::setActiveAuto(bool activeAuto) { void HyprsunsetManager::setActiveAuto(bool activeAuto) {
if (activeAuto == m_activeAuto) if (activeAuto == m_activeAuto) return;
return;
m_activeAuto = activeAuto; m_activeAuto = activeAuto;
emit activeAutoChanged(); emit activeAutoChanged();
} }
void HyprsunsetManager::setManualToggle(bool toggle) { void HyprsunsetManager::setManualToggle(bool toggle) {
if (toggle == m_manualToggle) if (toggle == m_manualToggle) return;
return;
m_manualToggle = toggle; m_manualToggle = toggle;
emit manualToggleChanged(); emit manualToggleChanged();
@@ -69,8 +67,7 @@ void HyprsunsetManager::setManualToggle(bool toggle) {
} }
void HyprsunsetManager::setEndTime(const int& time) { void HyprsunsetManager::setEndTime(const int& time) {
if (time == m_endTime) if (time == m_endTime) return;
return;
m_endTime = time; m_endTime = time;
emit endTimeChanged(); emit endTimeChanged();
@@ -78,8 +75,7 @@ void HyprsunsetManager::setEndTime(const int& time) {
} }
void HyprsunsetManager::setStartTime(const int& time) { void HyprsunsetManager::setStartTime(const int& time) {
if (time == m_startTime) if (time == m_startTime) return;
return;
m_startTime = time; m_startTime = time;
emit startTimeChanged(); emit startTimeChanged();
@@ -87,8 +83,7 @@ void HyprsunsetManager::setStartTime(const int& time) {
} }
void HyprsunsetManager::setTemp(const int& temp) { void HyprsunsetManager::setTemp(const int& temp) {
if (temp == m_temp) if (temp == m_temp) return;
return;
m_temp = temp; m_temp = temp;
emit tempChanged(); emit tempChanged();
@@ -104,8 +99,7 @@ void HyprsunsetManager::toggle() {
} }
void HyprsunsetManager::start() { void HyprsunsetManager::start() {
if (m_enabled && m_initialized) if (m_enabled && m_initialized) return;
return;
m_initialized = true; m_initialized = true;
m_enabled = true; m_enabled = true;
@@ -113,13 +107,13 @@ void HyprsunsetManager::start() {
emit enabledChanged(); emit enabledChanged();
m_process.setProgram("hyprctl"); m_process.setProgram("hyprctl");
m_process.setArguments({"hyprsunset", "temperature", QString::number(m_temp)}); m_process.setArguments(
{"hyprsunset", "temperature", QString::number(m_temp)});
m_process.startDetached(); m_process.startDetached();
} }
void HyprsunsetManager::end() { void HyprsunsetManager::end() {
if (!m_enabled && m_initialized) if (!m_enabled && m_initialized) return;
return;
m_initialized = true; m_initialized = true;
m_enabled = false; m_enabled = false;
@@ -132,12 +126,11 @@ void HyprsunsetManager::end() {
} }
void HyprsunsetManager::apply() { void HyprsunsetManager::apply() {
if (m_manualToggle || !m_activeAuto || !m_startAllowed) if (m_manualToggle || !m_activeAuto || !m_startAllowed) return;
return;
const auto current = QTime::currentTime(); const auto current = QTime::currentTime();
const auto currentMin = current.hour() * 60 + current.minute(); const auto currentMin = current.hour() * 60 + current.minute();
bool isDarkTime; bool isDarkTime = false;
if (m_startTime <= m_endTime) { if (m_startTime <= m_endTime) {
isDarkTime = (currentMin >= m_startTime && currentMin < m_endTime); isDarkTime = (currentMin >= m_startTime && currentMin < m_endTime);
@@ -152,4 +145,4 @@ void HyprsunsetManager::apply() {
} }
} }
}; }; // namespace ZShell::services
+51 -46
View File
@@ -10,57 +10,62 @@
namespace ZShell::services { namespace ZShell::services {
class HyprsunsetManager : public QObject { class HyprsunsetManager : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged) Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged)
Q_PROPERTY(int startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged) Q_PROPERTY(
Q_PROPERTY(int endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged) int startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged)
Q_PROPERTY(int temp READ temp WRITE setTemp NOTIFY tempChanged) Q_PROPERTY(int endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged)
Q_PROPERTY(bool activeAuto READ activeAuto WRITE setActiveAuto NOTIFY activeAutoChanged) Q_PROPERTY(int temp READ temp WRITE setTemp NOTIFY tempChanged)
Q_PROPERTY(bool manualToggle READ manualToggle WRITE setManualToggle NOTIFY manualToggleChanged) Q_PROPERTY(
bool activeAuto READ activeAuto WRITE setActiveAuto NOTIFY
activeAutoChanged)
Q_PROPERTY(
bool manualToggle READ manualToggle WRITE setManualToggle NOTIFY
manualToggleChanged)
public: public:
explicit HyprsunsetManager(QObject* parent = nullptr); explicit HyprsunsetManager(QObject* parent = nullptr);
[[nodiscard]] int startTime() const; [[nodiscard]] int startTime() const;
[[nodiscard]] int endTime() const; [[nodiscard]] int endTime() const;
[[nodiscard]] bool enabled() const; [[nodiscard]] bool enabled() const;
[[nodiscard]] int temp() const; [[nodiscard]] int temp() const;
[[nodiscard]] bool activeAuto() const; [[nodiscard]] bool activeAuto() const;
[[nodiscard]] bool manualToggle() const; [[nodiscard]] bool manualToggle() const;
Q_INVOKABLE void toggle(); Q_INVOKABLE void toggle();
Q_INVOKABLE void apply(); Q_INVOKABLE void apply();
void setStartTime(const int& time); void setStartTime(const int& time);
void setEndTime(const int& time); void setEndTime(const int& time);
void setTemp(const int& temp); void setTemp(const int& temp);
void setActiveAuto(bool activeAuto); void setActiveAuto(bool activeAuto);
void setManualToggle(bool toggle); void setManualToggle(bool toggle);
signals: signals:
void enabledChanged(); void enabledChanged();
void startTimeChanged(); void startTimeChanged();
void activeAutoChanged(); void activeAutoChanged();
void endTimeChanged(); void endTimeChanged();
void tempChanged(); void tempChanged();
void manualToggleChanged(); void manualToggleChanged();
private: private:
int m_startTime; int m_startTime;
int m_endTime; int m_endTime;
bool m_enabled = false; bool m_enabled = false;
bool m_manualToggle = false; bool m_manualToggle = false;
bool m_activeAuto; bool m_activeAuto;
bool m_startAllowed = false; bool m_startAllowed = false;
bool m_initialized = false; bool m_initialized = false;
QTimer m_startCooldown; QTimer m_startCooldown;
int m_temp; int m_temp;
QProcess m_process; QProcess m_process;
QTimer m_timer; QTimer m_timer;
QTimer m_manualTimer; QTimer m_manualTimer;
void start(); void start();
void end(); void end();
}; };
}; }; // namespace ZShell::services
+5 -5
View File
@@ -5,9 +5,7 @@
namespace ZShell::services { namespace ZShell::services {
Memory::Memory(QObject* parent) Memory::Memory(QObject* parent) : TickingService(parent) {}
: TickingService(parent) {
}
qreal Memory::used() const { qreal Memory::used() const {
return m_used; return m_used;
@@ -29,8 +27,10 @@ void Memory::tick() {
const QByteArray data = f.readAll(); const QByteArray data = f.readAll();
f.close(); f.close();
static const QRegularExpression reTotal(QStringLiteral("MemTotal: *(\\d+)")); static const QRegularExpression reTotal(
static const QRegularExpression reAvail(QStringLiteral("MemAvailable: *(\\d+)")); QStringLiteral("MemTotal: *(\\d+)"));
static const QRegularExpression reAvail(
QStringLiteral("MemAvailable: *(\\d+)"));
const QString text = QString::fromLatin1(data); const QString text = QString::fromLatin1(data);
const auto totalMatch = reTotal.match(text); const auto totalMatch = reTotal.match(text);
+20 -20
View File
@@ -8,32 +8,32 @@
namespace ZShell::services { namespace ZShell::services {
class Memory : public TickingService { class Memory : public TickingService {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_SINGLETON QML_SINGLETON
Q_PROPERTY(qreal used READ used NOTIFY changed) Q_PROPERTY(qreal used READ used NOTIFY changed)
Q_PROPERTY(qreal total READ total NOTIFY changed) Q_PROPERTY(qreal total READ total NOTIFY changed)
Q_PROPERTY(qreal percentage READ percentage NOTIFY changed) Q_PROPERTY(qreal percentage READ percentage NOTIFY changed)
public: public:
explicit Memory(QObject* parent = nullptr); explicit Memory(QObject* parent = nullptr);
[[nodiscard]] qreal used() const; [[nodiscard]] qreal used() const;
[[nodiscard]] qreal total() const; [[nodiscard]] qreal total() const;
[[nodiscard]] qreal percentage() const; [[nodiscard]] qreal percentage() const;
signals: signals:
void changed(); void changed();
protected: protected:
void tick() override; void tick() override;
private: private:
qreal m_used = 0.0; qreal m_used = 0.0;
qreal m_total = 1.0; qreal m_total = 1.0;
quint64 m_lastUsed = 0; quint64 m_lastUsed = 0;
quint64 m_lastTotal = 0; quint64 m_lastTotal = 0;
}; };
} // namespace ZShell::services } // namespace ZShell::services
+32 -19
View File
@@ -14,7 +14,7 @@ namespace ZShell::services::sensorslib {
namespace { namespace {
std::atomic<bool> g_initOk{ false }; std::atomic<bool> g_initOk{false};
std::once_flag g_initFlag; std::once_flag g_initFlag;
void doInit() { void doInit() {
@@ -25,14 +25,16 @@ void doInit() {
} }
g_initOk.store(true, std::memory_order_release); g_initOk.store(true, std::memory_order_release);
std::atexit([] { std::atexit([] {
if (g_initOk.load(std::memory_order_acquire)) { if (g_initOk.load(std::memory_order_acquire)) {
sensors_cleanup(); sensors_cleanup();
} }
}); });
} }
[[nodiscard]] std::optional<double> readTempInput(const sensors_chip_name* chip, const sensors_feature* feat) { [[nodiscard]] std::optional<double> readTempInput(
const sensors_subfeature* sf = sensors_get_subfeature(chip, feat, SENSORS_SUBFEATURE_TEMP_INPUT); const sensors_chip_name* chip, const sensors_feature* feat) {
const sensors_subfeature* sf =
sensors_get_subfeature(chip, feat, SENSORS_SUBFEATURE_TEMP_INPUT);
if (!sf) { if (!sf) {
return std::nullopt; return std::nullopt;
} }
@@ -43,7 +45,8 @@ void doInit() {
return value; return value;
} }
[[nodiscard]] QByteArray featureLabel(const sensors_chip_name* chip, const sensors_feature* feat) { [[nodiscard]] QByteArray featureLabel(
const sensors_chip_name* chip, const sensors_feature* feat) {
char* raw = sensors_get_label(chip, feat); char* raw = sensors_get_label(chip, feat);
if (!raw) { if (!raw) {
return {}; return {};
@@ -59,7 +62,8 @@ bool labelEquals(const QByteArray& label, const char* literal) {
bool labelStartsWith(const QByteArray& label, const char* prefix) { bool labelStartsWith(const QByteArray& label, const char* prefix) {
const auto n = std::strlen(prefix); const auto n = std::strlen(prefix);
return static_cast<size_t>(label.size()) >= n && std::memcmp(label.constData(), prefix, n) == 0; return static_cast<size_t>(label.size()) >= n &&
std::memcmp(label.constData(), prefix, n) == 0;
} }
} // namespace } // namespace
@@ -74,13 +78,15 @@ std::optional<double> cpuPackageTemp() {
return std::nullopt; return std::nullopt;
} }
std::optional<double> primary; // Package id N / Tdie std::optional<double> primary; // Package id N / Tdie
std::optional<double> fallback; // Tctl std::optional<double> fallback; // Tctl
int chipNr = 0; int chipNr = 0;
while (const sensors_chip_name* chip = sensors_get_detected_chips(nullptr, &chipNr)) { while (const sensors_chip_name* chip =
sensors_get_detected_chips(nullptr, &chipNr)) {
int featNr = 0; int featNr = 0;
while (const sensors_feature* feat = sensors_get_features(chip, &featNr)) { while (const sensors_feature* feat =
sensors_get_features(chip, &featNr)) {
if (feat->type != SENSORS_FEATURE_TEMP) { if (feat->type != SENSORS_FEATURE_TEMP) {
continue; continue;
} }
@@ -89,7 +95,8 @@ std::optional<double> cpuPackageTemp() {
continue; continue;
} }
if (labelStartsWith(label, "Package id ") || labelEquals(label, "Tdie")) { if (labelStartsWith(label, "Package id ") ||
labelEquals(label, "Tdie")) {
if (auto v = readTempInput(chip, feat)) { if (auto v = readTempInput(chip, feat)) {
primary = v; primary = v;
} }
@@ -116,13 +123,15 @@ std::optional<double> gpuPciAverageTemp() {
int countFallback = 0; int countFallback = 0;
int chipNr = 0; int chipNr = 0;
while (const sensors_chip_name* chip = sensors_get_detected_chips(nullptr, &chipNr)) { while (const sensors_chip_name* chip =
sensors_get_detected_chips(nullptr, &chipNr)) {
if (chip->bus.type != SENSORS_BUS_TYPE_PCI) { if (chip->bus.type != SENSORS_BUS_TYPE_PCI) {
continue; continue;
} }
int featNr = 0; int featNr = 0;
while (const sensors_feature* feat = sensors_get_features(chip, &featNr)) { while (const sensors_feature* feat =
sensors_get_features(chip, &featNr)) {
if (feat->type != SENSORS_FEATURE_TEMP) { if (feat->type != SENSORS_FEATURE_TEMP) {
continue; continue;
} }
@@ -131,10 +140,14 @@ std::optional<double> gpuPciAverageTemp() {
continue; continue;
} }
const bool tempIndexed = labelStartsWith(label, "temp") && label.size() > 4 && const bool tempIndexed =
std::isdigit(static_cast<unsigned char>(label[4])); labelStartsWith(label, "temp") && label.size() > 4 &&
const bool isPrimary = tempIndexed || labelEquals(label, "GPU core") || labelEquals(label, "edge"); std::isdigit(static_cast<unsigned char>(label[4]));
const bool isFallback = labelEquals(label, "junction") || labelEquals(label, "mem"); const bool isPrimary = tempIndexed ||
labelEquals(label, "GPU core") ||
labelEquals(label, "edge");
const bool isFallback = labelEquals(label, "junction") ||
labelEquals(label, "mem");
if (!isPrimary && !isFallback) { if (!isPrimary && !isFallback) {
continue; continue;
+1 -3
View File
@@ -5,9 +5,7 @@
namespace ZShell::services { namespace ZShell::services {
Service::Service(QObject* parent) Service::Service(QObject* parent) : QObject(parent) {}
: QObject(parent) {
}
void Service::ref(QObject* sender) { void Service::ref(QObject* sender) {
if (m_refs.isEmpty()) { if (m_refs.isEmpty()) {
+9 -9
View File
@@ -6,19 +6,19 @@
namespace ZShell::services { namespace ZShell::services {
class Service : public QObject { class Service : public QObject {
Q_OBJECT Q_OBJECT
public: public:
explicit Service(QObject* parent = nullptr); explicit Service(QObject* parent = nullptr);
void ref(QObject* sender); void ref(QObject* sender);
void unref(QObject* sender); void unref(QObject* sender);
private: private:
QSet<QObject*> m_refs; QSet<QObject*> m_refs;
virtual void start() = 0; virtual void start() = 0;
virtual void stop() = 0; virtual void stop() = 0;
}; };
} // namespace ZShell::services } // namespace ZShell::services
+1 -2
View File
@@ -5,8 +5,7 @@
namespace ZShell::services { namespace ZShell::services {
ServiceRef::ServiceRef(Service* service, QObject* parent) ServiceRef::ServiceRef(Service* service, QObject* parent)
: QObject(parent) : QObject(parent), m_service(service) {
, m_service(service) {
if (m_service) { if (m_service) {
m_service->ref(this); m_service->ref(this);
} }
+13 -11
View File
@@ -7,22 +7,24 @@
namespace ZShell::services { namespace ZShell::services {
class ServiceRef : public QObject { class ServiceRef : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(ZShell::services::Service* service READ service WRITE setService NOTIFY serviceChanged) Q_PROPERTY(
ZShell::services::Service* service READ service WRITE setService NOTIFY
serviceChanged)
public: public:
explicit ServiceRef(Service* service = nullptr, QObject* parent = nullptr); explicit ServiceRef(Service* service = nullptr, QObject* parent = nullptr);
[[nodiscard]] Service* service() const; [[nodiscard]] Service* service() const;
void setService(Service* service); void setService(Service* service);
signals: signals:
void serviceChanged(); void serviceChanged();
private: private:
QPointer<Service> m_service; QPointer<Service> m_service;
}; };
} // namespace ZShell::services } // namespace ZShell::services
+39 -41
View File
@@ -24,12 +24,14 @@ struct Accum {
}; };
[[nodiscard]] QString sysfsRealPath(uint major, uint minor) { [[nodiscard]] QString sysfsRealPath(uint major, uint minor) {
const QString link = QStringLiteral("/sys/dev/block/%1:%2").arg(major).arg(minor); const QString link =
QStringLiteral("/sys/dev/block/%1:%2").arg(major).arg(minor);
const QString resolved = QFileInfo(link).canonicalFilePath(); const QString resolved = QFileInfo(link).canonicalFilePath();
return resolved; return resolved;
} }
[[nodiscard]] bool readDevtFromSysfs(const QString& sysfsBlockDir, uint& major, uint& minor) { [[nodiscard]] bool readDevtFromSysfs(
const QString& sysfsBlockDir, uint& major, uint& minor) {
QFile f(sysfsBlockDir + QStringLiteral("/dev")); QFile f(sysfsBlockDir + QStringLiteral("/dev"));
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false; return false;
@@ -50,6 +52,7 @@ struct Accum {
QStringList resolveByDevt(uint major, uint minor, int depth = 0); QStringList resolveByDevt(uint major, uint minor, int depth = 0);
// NOLINTNEXTLINE(misc-no-recursion)
QStringList resolveAtNode(const QString& node, int depth) { QStringList resolveAtNode(const QString& node, int depth) {
if (node.isEmpty() || depth > 8) { if (node.isEmpty() || depth > 8) {
return {}; return {};
@@ -62,18 +65,20 @@ QStringList resolveAtNode(const QString& node, int depth) {
if (QFileInfo::exists(node + QStringLiteral("/partition"))) { if (QFileInfo::exists(node + QStringLiteral("/partition"))) {
const QString diskNode = nodeInfo.path(); const QString diskNode = nodeInfo.path();
return { QFileInfo(diskNode).fileName() }; return {QFileInfo(diskNode).fileName()};
} }
const QDir slavesDir(node + QStringLiteral("/slaves")); const QDir slavesDir(node + QStringLiteral("/slaves"));
if (slavesDir.exists()) { if (slavesDir.exists()) {
const QStringList slaves = slavesDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); const QStringList slaves =
slavesDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
if (!slaves.isEmpty()) { if (!slaves.isEmpty()) {
QStringList out; QStringList out;
for (const QString& slave : slaves) { for (const QString& slave : slaves) {
uint sm = 0; uint sm = 0;
uint sn = 0; uint sn = 0;
const QString slaveDir = QStringLiteral("/sys/class/block/") + slave; const QString slaveDir =
QStringLiteral("/sys/class/block/") + slave;
if (!readDevtFromSysfs(slaveDir, sm, sn)) { if (!readDevtFromSysfs(slaveDir, sm, sn)) {
continue; continue;
} }
@@ -88,18 +93,17 @@ QStringList resolveAtNode(const QString& node, int depth) {
} }
} }
return { nodeInfo.fileName() }; return {nodeInfo.fileName()};
} }
// NOLINTNEXTLINE(misc-no-recursion)
QStringList resolveByDevt(uint major, uint minor, int depth) { QStringList resolveByDevt(uint major, uint minor, int depth) {
return resolveAtNode(sysfsRealPath(major, minor), depth); return resolveAtNode(sysfsRealPath(major, minor), depth);
} }
} // namespace } // namespace
Storage::Storage(QObject* parent) Storage::Storage(QObject* parent) : TickingService(parent) {}
: TickingService(parent) {
}
qreal Storage::percentage() const { qreal Storage::percentage() const {
qreal totalUsed = 0.0; qreal totalUsed = 0.0;
@@ -124,7 +128,8 @@ bool Storage::sameOrder(const QList<DiskInfo*>& a, const QList<DiskInfo*>& b) {
} }
QQmlListProperty<DiskInfo> Storage::disksProp() { QQmlListProperty<DiskInfo> Storage::disksProp() {
return QQmlListProperty<DiskInfo>(this, nullptr, &Storage::disksCount, &Storage::disksAt); return QQmlListProperty<DiskInfo>(
this, nullptr, &Storage::disksCount, &Storage::disksAt);
} }
qsizetype Storage::disksCount(QQmlListProperty<DiskInfo>* prop) { qsizetype Storage::disksCount(QQmlListProperty<DiskInfo>* prop) {
@@ -157,30 +162,11 @@ DiskInfo* Storage::primaryDisk() const {
bool Storage::isPseudoFs(QByteArrayView fsType) { bool Storage::isPseudoFs(QByteArrayView fsType) {
static constexpr const char* kPseudo[] = { static constexpr const char* kPseudo[] = {
"tmpfs", "tmpfs", "devtmpfs", "proc", "sysfs", "cgroup",
"devtmpfs", "cgroup2", "overlay", "squashfs", "devpts", "mqueue",
"proc", "ramfs", "rpc_pipefs", "autofs", "configfs", "debugfs",
"sysfs", "tracefs", "securityfs", "pstore", "bpf", "binfmt_misc",
"cgroup", "hugetlbfs", "fusectl", "efivarfs", "selinuxfs",
"cgroup2",
"overlay",
"squashfs",
"devpts",
"mqueue",
"ramfs",
"rpc_pipefs",
"autofs",
"configfs",
"debugfs",
"tracefs",
"securityfs",
"pstore",
"bpf",
"binfmt_misc",
"hugetlbfs",
"fusectl",
"efivarfs",
"selinuxfs",
}; };
for (const char* p : kPseudo) { for (const char* p : kPseudo) {
if (fsType == QByteArrayView(p)) { if (fsType == QByteArrayView(p)) {
@@ -194,7 +180,7 @@ QStringList Storage::resolveToPhysicalDisks(const QString& devicePath) {
if (devicePath.isEmpty() || !devicePath.startsWith(QLatin1Char('/'))) { if (devicePath.isEmpty() || !devicePath.startsWith(QLatin1Char('/'))) {
return {}; return {};
} }
struct stat st {}; struct stat st{};
if (::stat(devicePath.toLocal8Bit().constData(), &st) != 0) { if (::stat(devicePath.toLocal8Bit().constData(), &st) != 0) {
return {}; return {};
} }
@@ -232,7 +218,8 @@ void Storage::tick() {
const QByteArray device = v.device(); const QByteArray device = v.device();
const auto totalBytes = static_cast<quint64>(v.bytesTotal()); const auto totalBytes = static_cast<quint64>(v.bytesTotal());
const auto availBytes = static_cast<quint64>(v.bytesAvailable()); const auto availBytes = static_cast<quint64>(v.bytesAvailable());
const quint64 usedBytes = totalBytes > availBytes ? totalBytes - availBytes : 0; const quint64 usedBytes =
totalBytes > availBytes ? totalBytes - availBytes : 0;
const bool isRoot = v.rootPath() == QStringLiteral("/"); const bool isRoot = v.rootPath() == QStringLiteral("/");
DeviceEntry& e = byDevice[device]; DeviceEntry& e = byDevice[device];
@@ -244,7 +231,8 @@ void Storage::tick() {
for (auto it = byDevice.constBegin(); it != byDevice.constEnd(); ++it) { for (auto it = byDevice.constBegin(); it != byDevice.constEnd(); ++it) {
const DeviceEntry& e = it.value(); const DeviceEntry& e = it.value();
const QStringList disks = resolveToPhysicalDisks(QString::fromLocal8Bit(e.device)); const QStringList disks =
resolveToPhysicalDisks(QString::fromLocal8Bit(e.device));
if (disks.isEmpty()) { if (disks.isEmpty()) {
continue; continue;
} }
@@ -269,14 +257,23 @@ void Storage::tick() {
next.reserve(byDisk.size()); next.reserve(byDisk.size());
for (auto it = byDisk.constBegin(); it != byDisk.constEnd(); ++it) { for (auto it = byDisk.constBegin(); it != byDisk.constEnd(); ++it) {
if (DiskInfo* survivor = existing.take(it.key())) { if (DiskInfo* survivor = existing.take(it.key())) {
survivor->update(it.value().usedBytes, it.value().totalBytes, it.value().hasRoot); survivor->update(
it.value().usedBytes,
it.value().totalBytes,
it.value().hasRoot);
next.append(survivor); next.append(survivor);
} else { } else {
next.append(new DiskInfo(it.key(), it.value().usedBytes, it.value().totalBytes, it.value().hasRoot, this)); next.append(new DiskInfo(
it.key(),
it.value().usedBytes,
it.value().totalBytes,
it.value().hasRoot,
this));
} }
} }
std::sort(next.begin(), next.end(), [](const DiskInfo* a, const DiskInfo* b) { std::sort(
next.begin(), next.end(), [](const DiskInfo* a, const DiskInfo* b) {
if (a->hasRoot() != b->hasRoot()) { if (a->hasRoot() != b->hasRoot()) {
return a->hasRoot(); return a->hasRoot();
} }
@@ -284,7 +281,8 @@ void Storage::tick() {
}); });
bool manualCleared = false; bool manualCleared = false;
if (DiskInfo* m = m_manualPrimaryDisk.data(); m && existing.contains(m->mount())) { if (DiskInfo* m = m_manualPrimaryDisk.data();
m && existing.contains(m->mount())) {
m_manualPrimaryDisk.clear(); m_manualPrimaryDisk.clear();
manualCleared = true; manualCleared = true;
} }
+37 -30
View File
@@ -12,44 +12,51 @@
namespace ZShell::services { namespace ZShell::services {
class Storage : public TickingService { class Storage : public TickingService {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_SINGLETON QML_SINGLETON
Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged) Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged)
Q_PROPERTY(QQmlListProperty<ZShell::services::DiskInfo> disks READ disksProp NOTIFY disksChanged) Q_PROPERTY(
Q_PROPERTY(ZShell::services::DiskInfo* manualPrimaryDisk READ manualPrimaryDisk WRITE setManualPrimaryDisk NOTIFY QQmlListProperty<ZShell::services::DiskInfo> disks READ disksProp NOTIFY
manualPrimaryDiskChanged) disksChanged)
Q_PROPERTY(ZShell::services::DiskInfo* primaryDisk READ primaryDisk NOTIFY primaryDiskChanged) Q_PROPERTY(
ZShell::services::DiskInfo* manualPrimaryDisk READ manualPrimaryDisk
WRITE setManualPrimaryDisk NOTIFY manualPrimaryDiskChanged)
Q_PROPERTY(
ZShell::services::DiskInfo* primaryDisk READ primaryDisk NOTIFY
primaryDiskChanged)
public: public:
explicit Storage(QObject* parent = nullptr); explicit Storage(QObject* parent = nullptr);
[[nodiscard]] qreal percentage() const; [[nodiscard]] qreal percentage() const;
[[nodiscard]] QQmlListProperty<DiskInfo> disksProp(); [[nodiscard]] QQmlListProperty<DiskInfo> disksProp();
[[nodiscard]] DiskInfo* manualPrimaryDisk() const; [[nodiscard]] DiskInfo* manualPrimaryDisk() const;
void setManualPrimaryDisk(DiskInfo* disk); void setManualPrimaryDisk(DiskInfo* disk);
[[nodiscard]] DiskInfo* primaryDisk() const; [[nodiscard]] DiskInfo* primaryDisk() const;
signals: signals:
void disksChanged(); void disksChanged();
void percentageChanged(); void percentageChanged();
void manualPrimaryDiskChanged(); void manualPrimaryDiskChanged();
void primaryDiskChanged(); void primaryDiskChanged();
protected: protected:
void tick() override; void tick() override;
private: private:
[[nodiscard]] static QStringList resolveToPhysicalDisks(const QString& devicePath); [[nodiscard]] static QStringList resolveToPhysicalDisks(
[[nodiscard]] static bool isPseudoFs(QByteArrayView fsType); const QString& devicePath);
[[nodiscard]] static bool sameOrder(const QList<DiskInfo*>& a, const QList<DiskInfo*>& b); [[nodiscard]] static bool isPseudoFs(QByteArrayView fsType);
[[nodiscard]] static bool sameOrder(
const QList<DiskInfo*>& a, const QList<DiskInfo*>& b);
static qsizetype disksCount(QQmlListProperty<DiskInfo>* prop); static qsizetype disksCount(QQmlListProperty<DiskInfo>* prop);
static DiskInfo* disksAt(QQmlListProperty<DiskInfo>* prop, qsizetype i); static DiskInfo* disksAt(QQmlListProperty<DiskInfo>* prop, qsizetype i);
QList<DiskInfo*> m_disks; QList<DiskInfo*> m_disks;
QPointer<DiskInfo> m_manualPrimaryDisk; QPointer<DiskInfo> m_manualPrimaryDisk;
}; };
} // namespace ZShell::services } // namespace ZShell::services
+35 -26
View File
@@ -10,43 +10,52 @@
namespace ZShell::services { namespace ZShell::services {
TickingService::TickingService(QObject* parent) TickingService::TickingService(QObject* parent)
: Service(parent) : Service(parent), m_timer(new QTimer(this)) {
, m_timer(new QTimer(this)) {
m_timer->setSingleShot(false); m_timer->setSingleShot(false);
QObject::connect(m_timer, &QTimer::timeout, this, [this] { QObject::connect(m_timer, &QTimer::timeout, this, [this] { tick(); });
tick();
});
QString configPath = QDir::homePath() + QStringLiteral("/.config/zshell/config.json"); QString configPath =
QDir::homePath() + QStringLiteral("/.config/zshell/config.json");
auto reloadConfig = [this, configPath]() { auto reloadConfig = [this, configPath]() {
QFile file(configPath); QFile file(configPath);
if (file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); return;
if (!doc.isNull()) { }
QJsonObject dashboard = doc.object().value("dashboard").toObject(); QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
if (dashboard.contains("resourceUpdateInterval")) { if (doc.isNull()) {
applyInterval(dashboard.value("resourceUpdateInterval").toInt(1000)); return;
} }
} QJsonObject dashboard = doc.object().value("dashboard").toObject();
} if (dashboard.contains("resourceUpdateInterval")) {
}; applyInterval(
dashboard.value("resourceUpdateInterval").toInt(1000));
}
};
reloadConfig(); reloadConfig();
static auto* watcher = new QFileSystemWatcher(); static auto* watcher = new QFileSystemWatcher();
if (!watcher->files().contains(configPath)) { if (!watcher->files().contains(configPath)) {
QObject::connect(watcher, &QFileSystemWatcher::fileChanged, this, [this, configPath]() { QObject::connect(
watcher,
&QFileSystemWatcher::fileChanged,
this,
[this, configPath]() {
QTimer::singleShot(100, this, [this, configPath]() { QTimer::singleShot(100, this, [this, configPath]() {
QFile file(configPath); QFile file(configPath);
if (file.exists()) { if (!file.exists()) {
QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); return;
if (!doc.isNull()) { }
QJsonObject dashboard = doc.object().value("dashboard").toObject(); QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
if (dashboard.contains("resourceUpdateInterval")) { if (doc.isNull()) {
applyInterval(dashboard.value("resourceUpdateInterval").toInt(1000)); return;
} }
} QJsonObject dashboard =
doc.object().value("dashboard").toObject();
if (dashboard.contains("resourceUpdateInterval")) {
applyInterval(dashboard.value("resourceUpdateInterval")
.toInt(1000));
} }
}); });
}); });
+17 -16
View File
@@ -6,30 +6,31 @@
namespace ZShell::services { namespace ZShell::services {
class TickingService : public Service { class TickingService : public Service {
Q_OBJECT Q_OBJECT
Q_PROPERTY(int updateInterval READ updateInterval NOTIFY updateIntervalChanged) Q_PROPERTY(
int updateInterval READ updateInterval NOTIFY updateIntervalChanged)
public: public:
explicit TickingService(QObject* parent = nullptr); explicit TickingService(QObject* parent = nullptr);
[[nodiscard]] int updateInterval() const; [[nodiscard]] int updateInterval() const;
signals: signals:
void updateIntervalChanged(); void updateIntervalChanged();
protected: protected:
void start() final; void start() final;
void stop() final; void stop() final;
virtual void tick() = 0; virtual void tick() = 0;
private: private:
void applyInterval(int ms); void applyInterval(int ms);
QTimer* m_timer; QTimer* m_timer;
int m_interval = 1000; int m_interval = 1000;
bool m_running = false; bool m_running = false;
}; };
} // namespace ZShell::services } // namespace ZShell::services
+5 -5
View File
@@ -16,18 +16,18 @@ namespace ZShell::services::usagefmt {
FormatResult UsageFmt::formatKib(qreal kib, qreal total) const { FormatResult UsageFmt::formatKib(qreal kib, qreal total) const {
if (!finitePositive(kib) || !finitePositive(total)) { if (!finitePositive(kib) || !finitePositive(total)) {
return { 0.0, 0.0, "KiB" }; return {0.0, 0.0, "KiB"};
} }
if (total >= kGib) { if (total >= kGib) {
return { kib / kGib, total / kGib, "TiB" }; return {kib / kGib, total / kGib, "TiB"};
} }
if (total >= kMib) { if (total >= kMib) {
return { kib / kMib, total / kMib, "GiB" }; return {kib / kMib, total / kMib, "GiB"};
} }
if (total >= kKib) { if (total >= kKib) {
return { kib / kKib, total / kKib, "MiB" }; return {kib / kKib, total / kKib, "MiB"};
} }
return { kib, total, "KiB" }; return {kib, total, "KiB"};
} }
} // namespace ZShell::services::usagefmt } // namespace ZShell::services::usagefmt
+7 -6
View File
@@ -15,19 +15,20 @@ struct FormatResult {
Q_PROPERTY(qreal total MEMBER total CONSTANT) Q_PROPERTY(qreal total MEMBER total CONSTANT)
Q_PROPERTY(QString unit MEMBER unit CONSTANT) Q_PROPERTY(QString unit MEMBER unit CONSTANT)
public: public:
qreal value; qreal value;
qreal total; qreal total;
QString unit; QString unit;
}; };
class UsageFmt : public QObject { class UsageFmt : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_SINGLETON QML_SINGLETON
public: public:
Q_INVOKABLE [[nodiscard]] FormatResult formatKib(qreal kib, qreal total) const; Q_INVOKABLE [[nodiscard]] FormatResult formatKib(
qreal kib, qreal total) const;
}; };
} // namespace ZShell::services::usagefmt } // namespace ZShell::services::usagefmt
+170 -157
View File
@@ -7,259 +7,272 @@
namespace ZShell { namespace ZShell {
AppEntry::AppEntry(QObject* entry, unsigned int frequency, QObject* parent) AppEntry::AppEntry(QObject* entry, unsigned int frequency, QObject* parent)
: QObject(parent) : QObject(parent), m_entry(entry), m_frequency(frequency) {
, m_entry(entry) const auto mo = m_entry->metaObject();
, m_frequency(frequency) { // NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.VirtualCall)
const auto mo = m_entry->metaObject(); const auto tmo = metaObject();
const auto tmo = metaObject();
for (const auto& prop : for (const auto& prop :
{ "name", "comment", "execString", "startupClass", "genericName", "categories", "keywords" }) { {"name",
const auto metaProp = mo->property(mo->indexOfProperty(prop)); "comment",
const auto thisMetaProp = tmo->property(tmo->indexOfProperty(prop)); "execString",
QObject::connect(m_entry, metaProp.notifySignal(), this, thisMetaProp.notifySignal()); "startupClass",
} "genericName",
"categories",
"keywords"}) {
const auto metaProp = mo->property(mo->indexOfProperty(prop));
const auto thisMetaProp = tmo->property(tmo->indexOfProperty(prop));
QObject::connect(
m_entry, metaProp.notifySignal(), this, thisMetaProp.notifySignal());
}
QObject::connect(m_entry, &QObject::destroyed, this, [this]() { QObject::connect(m_entry, &QObject::destroyed, this, [this]() {
m_entry = nullptr; m_entry = nullptr;
deleteLater(); deleteLater();
}); });
} }
QObject* AppEntry::entry() const { QObject* AppEntry::entry() const {
return m_entry; return m_entry;
} }
quint32 AppEntry::frequency() const { quint32 AppEntry::frequency() const {
return m_frequency; return m_frequency;
} }
void AppEntry::setFrequency(unsigned int frequency) { void AppEntry::setFrequency(unsigned int frequency) {
if (m_frequency != frequency) { if (m_frequency != frequency) {
m_frequency = frequency; m_frequency = frequency;
emit frequencyChanged(); emit frequencyChanged();
} }
} }
void AppEntry::incrementFrequency() { void AppEntry::incrementFrequency() {
m_frequency++; m_frequency++;
emit frequencyChanged(); emit frequencyChanged();
} }
QString AppEntry::id() const { QString AppEntry::id() const {
if (!m_entry) { if (!m_entry) {
return ""; return "";
} }
return m_entry->property("id").toString(); return m_entry->property("id").toString();
} }
QString AppEntry::name() const { QString AppEntry::name() const {
if (!m_entry) { if (!m_entry) {
return ""; return "";
} }
return m_entry->property("name").toString(); return m_entry->property("name").toString();
} }
QString AppEntry::comment() const { QString AppEntry::comment() const {
if (!m_entry) { if (!m_entry) {
return ""; return "";
} }
return m_entry->property("comment").toString(); return m_entry->property("comment").toString();
} }
QString AppEntry::execString() const { QString AppEntry::execString() const {
if (!m_entry) { if (!m_entry) {
return ""; return "";
} }
return m_entry->property("execString").toString(); return m_entry->property("execString").toString();
} }
QString AppEntry::startupClass() const { QString AppEntry::startupClass() const {
if (!m_entry) { if (!m_entry) {
return ""; return "";
} }
return m_entry->property("startupClass").toString(); return m_entry->property("startupClass").toString();
} }
QString AppEntry::genericName() const { QString AppEntry::genericName() const {
if (!m_entry) { if (!m_entry) {
return ""; return "";
} }
return m_entry->property("genericName").toString(); return m_entry->property("genericName").toString();
} }
QString AppEntry::categories() const { QString AppEntry::categories() const {
if (!m_entry) { if (!m_entry) {
return ""; return "";
} }
return m_entry->property("categories").toStringList().join(" "); return m_entry->property("categories").toStringList().join(" ");
} }
QString AppEntry::keywords() const { QString AppEntry::keywords() const {
if (!m_entry) { if (!m_entry) {
return ""; return "";
} }
return m_entry->property("keywords").toStringList().join(" "); return m_entry->property("keywords").toStringList().join(" ");
} }
AppDb::AppDb(QObject* parent) AppDb::AppDb(QObject* parent)
: QObject(parent) : QObject(parent)
, m_timer(new QTimer(this)) , m_timer(new QTimer(this))
, m_uuid(QUuid::createUuid().toString()) { , m_uuid(QUuid::createUuid().toString()) {
m_timer->setSingleShot(true); m_timer->setSingleShot(true);
m_timer->setInterval(300); m_timer->setInterval(300);
QObject::connect(m_timer, &QTimer::timeout, this, &AppDb::updateApps); QObject::connect(m_timer, &QTimer::timeout, this, &AppDb::updateApps);
auto db = QSqlDatabase::addDatabase("QSQLITE", m_uuid); auto db = QSqlDatabase::addDatabase("QSQLITE", m_uuid);
db.setDatabaseName(":memory:"); db.setDatabaseName(":memory:");
db.open(); db.open();
QSqlQuery query(db); QSqlQuery query(db);
query.exec("CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, frequency INTEGER)"); query.exec(
"CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, "
"frequency INTEGER)");
} }
QString AppDb::uuid() const { QString AppDb::uuid() const {
return m_uuid; return m_uuid;
} }
QString AppDb::path() const { QString AppDb::path() const {
return m_path; return m_path;
} }
void AppDb::setPath(const QString& path) { void AppDb::setPath(const QString& path) {
auto newPath = path.isEmpty() ? ":memory:" : path; auto newPath = path.isEmpty() ? ":memory:" : path;
if (m_path == newPath) { if (m_path == newPath) {
return; return;
} }
m_path = newPath; m_path = newPath;
emit pathChanged(); emit pathChanged();
auto db = QSqlDatabase::database(m_uuid, false); auto db = QSqlDatabase::database(m_uuid, false);
db.close(); db.close();
db.setDatabaseName(newPath); db.setDatabaseName(newPath);
db.open(); db.open();
QSqlQuery query(db); QSqlQuery query(db);
query.exec("CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, frequency INTEGER)"); query.exec(
"CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, "
"frequency INTEGER)");
updateAppFrequencies(); updateAppFrequencies();
} }
QObjectList AppDb::entries() const { QObjectList AppDb::entries() const {
return m_entries; return m_entries;
} }
void AppDb::setEntries(const QObjectList& entries) { void AppDb::setEntries(const QObjectList& entries) {
if (m_entries == entries) { if (m_entries == entries) {
return; return;
} }
m_entries = entries; m_entries = entries;
emit entriesChanged(); emit entriesChanged();
m_timer->start(); m_timer->start();
} }
QQmlListProperty<AppEntry> AppDb::apps() { QQmlListProperty<AppEntry> AppDb::apps() {
return QQmlListProperty<AppEntry>(this, &getSortedApps()); return QQmlListProperty<AppEntry>(this, &getSortedApps());
} }
void AppDb::incrementFrequency(const QString& id) { void AppDb::incrementFrequency(const QString& id) {
auto db = QSqlDatabase::database(m_uuid); auto db = QSqlDatabase::database(m_uuid);
QSqlQuery query(db); QSqlQuery query(db);
query.prepare("INSERT INTO frequencies (id, frequency) " query.prepare(
"VALUES (:id, 1) " "INSERT INTO frequencies (id, frequency) "
"ON CONFLICT (id) DO UPDATE SET frequency = frequency + 1"); "VALUES (:id, 1) "
query.bindValue(":id", id); "ON CONFLICT (id) DO UPDATE SET frequency = frequency + 1");
query.exec(); query.bindValue(":id", id);
query.exec();
auto* app = m_apps.value(id); auto* app = m_apps.value(id);
if (app) { if (app) {
const auto before = getSortedApps(); const auto before = getSortedApps();
app->incrementFrequency(); app->incrementFrequency();
if (before != getSortedApps()) { if (before != getSortedApps()) {
emit appsChanged(); emit appsChanged();
} }
} else { } else {
qWarning() << "AppDb::incrementFrequency: could not find app with id" << id; qWarning() << "AppDb::incrementFrequency: could not find app with id"
} << id;
}
} }
QList<AppEntry*>& AppDb::getSortedApps() const { QList<AppEntry*>& AppDb::getSortedApps() const {
m_sortedApps = m_apps.values(); m_sortedApps = m_apps.values();
std::sort(m_sortedApps.begin(), m_sortedApps.end(), [](AppEntry* a, AppEntry* b) { std::sort(
if (a->frequency() != b->frequency()) { m_sortedApps.begin(), m_sortedApps.end(), [](AppEntry* a, AppEntry* b) {
return a->frequency() > b->frequency(); if (a->frequency() != b->frequency()) {
} return a->frequency() > b->frequency();
return a->name().localeAwareCompare(b->name()) < 0; }
}); return a->name().localeAwareCompare(b->name()) < 0;
return m_sortedApps; });
return m_sortedApps;
} }
quint32 AppDb::getFrequency(const QString& id) const { quint32 AppDb::getFrequency(const QString& id) const {
auto db = QSqlDatabase::database(m_uuid); auto db = QSqlDatabase::database(m_uuid);
QSqlQuery query(db); QSqlQuery query(db);
query.prepare("SELECT frequency FROM frequencies WHERE id = :id"); query.prepare("SELECT frequency FROM frequencies WHERE id = :id");
query.bindValue(":id", id); query.bindValue(":id", id);
if (query.exec() && query.next()) { if (query.exec() && query.next()) {
return query.value(0).toUInt(); return query.value(0).toUInt();
} }
return 0; return 0;
} }
void AppDb::updateAppFrequencies() { void AppDb::updateAppFrequencies() {
const auto before = getSortedApps(); const auto before = getSortedApps();
for (auto* app : std::as_const(m_apps)) { for (auto* app : std::as_const(m_apps)) {
app->setFrequency(getFrequency(app->id())); app->setFrequency(getFrequency(app->id()));
} }
if (before != getSortedApps()) { if (before != getSortedApps()) {
emit appsChanged(); emit appsChanged();
} }
} }
void AppDb::updateApps() { void AppDb::updateApps() {
bool dirty = false; bool dirty = false;
for (const auto& entry : std::as_const(m_entries)) { for (const auto& entry : std::as_const(m_entries)) {
const auto id = entry->property("id").toString(); const auto id = entry->property("id").toString();
if (!m_apps.contains(id)) { if (!m_apps.contains(id)) {
dirty = true; dirty = true;
auto* const newEntry = new AppEntry(entry, getFrequency(id), this); auto* const newEntry = new AppEntry(entry, getFrequency(id), this);
QObject::connect(newEntry, &QObject::destroyed, this, [id, this]() { QObject::connect(newEntry, &QObject::destroyed, this, [id, this]() {
if (m_apps.remove(id)) { if (m_apps.remove(id)) {
emit appsChanged(); emit appsChanged();
} }
}); });
m_apps.insert(id, newEntry); m_apps.insert(id, newEntry);
} }
} }
QSet<QString> newIds; QSet<QString> newIds;
for (const auto& entry : std::as_const(m_entries)) { for (const auto& entry : std::as_const(m_entries)) {
newIds.insert(entry->property("id").toString()); newIds.insert(entry->property("id").toString());
} }
for (auto it = m_apps.keyBegin(); it != m_apps.keyEnd(); ++it) { for (auto it = m_apps.keyBegin(); it != m_apps.keyEnd(); ++it) {
const auto& id = *it; const auto& id = *it;
if (!newIds.contains(id)) { if (!newIds.contains(id)) {
dirty = true; dirty = true;
m_apps.take(id)->deleteLater(); m_apps.take(id)->deleteLater();
} }
} }
if (dirty) { if (dirty) {
emit appsChanged(); emit appsChanged();
} }
} }
} // namespace ZShell } // namespace ZShell
+74 -70
View File
@@ -9,98 +9,102 @@
namespace ZShell { namespace ZShell {
class AppEntry : public QObject { class AppEntry : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("AppEntry instances can only be retrieved from an AppDb") QML_UNCREATABLE("AppEntry instances can only be retrieved from an AppDb")
// The actual DesktopEntry, but we don't have access to the type so it's a QObject // The actual DesktopEntry, but we don't have access to the type so it's a QObject
Q_PROPERTY(QObject* entry READ entry CONSTANT) Q_PROPERTY(QObject* entry READ entry CONSTANT)
Q_PROPERTY(quint32 frequency READ frequency NOTIFY frequencyChanged) Q_PROPERTY(quint32 frequency READ frequency NOTIFY frequencyChanged)
Q_PROPERTY(QString id READ id CONSTANT) Q_PROPERTY(QString id READ id CONSTANT)
Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QString comment READ comment NOTIFY commentChanged) Q_PROPERTY(QString comment READ comment NOTIFY commentChanged)
Q_PROPERTY(QString execString READ execString NOTIFY execStringChanged) Q_PROPERTY(QString execString READ execString NOTIFY execStringChanged)
Q_PROPERTY(QString startupClass READ startupClass NOTIFY startupClassChanged) Q_PROPERTY(QString startupClass READ startupClass NOTIFY startupClassChanged)
Q_PROPERTY(QString genericName READ genericName NOTIFY genericNameChanged) Q_PROPERTY(QString genericName READ genericName NOTIFY genericNameChanged)
Q_PROPERTY(QString categories READ categories NOTIFY categoriesChanged) Q_PROPERTY(QString categories READ categories NOTIFY categoriesChanged)
Q_PROPERTY(QString keywords READ keywords NOTIFY keywordsChanged) Q_PROPERTY(QString keywords READ keywords NOTIFY keywordsChanged)
public: public:
explicit AppEntry(QObject* entry, quint32 frequency, QObject* parent = nullptr); explicit AppEntry(
QObject* entry, quint32 frequency, QObject* parent = nullptr);
[[nodiscard]] QObject* entry() const; [[nodiscard]] QObject* entry() const;
[[nodiscard]] quint32 frequency() const; [[nodiscard]] quint32 frequency() const;
void setFrequency(quint32 frequency); void setFrequency(quint32 frequency);
void incrementFrequency(); void incrementFrequency();
[[nodiscard]] QString id() const; [[nodiscard]] QString id() const;
[[nodiscard]] QString name() const; [[nodiscard]] QString name() const;
[[nodiscard]] QString comment() const; [[nodiscard]] QString comment() const;
[[nodiscard]] QString execString() const; [[nodiscard]] QString execString() const;
[[nodiscard]] QString startupClass() const; [[nodiscard]] QString startupClass() const;
[[nodiscard]] QString genericName() const; [[nodiscard]] QString genericName() const;
[[nodiscard]] QString categories() const; [[nodiscard]] QString categories() const;
[[nodiscard]] QString keywords() const; [[nodiscard]] QString keywords() const;
signals: signals:
void frequencyChanged(); void frequencyChanged();
void nameChanged(); void nameChanged();
void commentChanged(); void commentChanged();
void execStringChanged(); void execStringChanged();
void startupClassChanged(); void startupClassChanged();
void genericNameChanged(); void genericNameChanged();
void categoriesChanged(); void categoriesChanged();
void keywordsChanged(); void keywordsChanged();
private: private:
QObject* m_entry; QObject* m_entry;
quint32 m_frequency; quint32 m_frequency;
}; };
class AppDb : public QObject { class AppDb : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(QString uuid READ uuid CONSTANT) Q_PROPERTY(QString uuid READ uuid CONSTANT)
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED) Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED)
Q_PROPERTY(QObjectList entries READ entries WRITE setEntries NOTIFY entriesChanged REQUIRED) Q_PROPERTY(
Q_PROPERTY(QQmlListProperty<ZShell::AppEntry> apps READ apps NOTIFY appsChanged) QObjectList entries READ entries WRITE setEntries NOTIFY entriesChanged
REQUIRED)
Q_PROPERTY(
QQmlListProperty<ZShell::AppEntry> apps READ apps NOTIFY appsChanged)
public: public:
explicit AppDb(QObject* parent = nullptr); explicit AppDb(QObject* parent = nullptr);
[[nodiscard]] QString uuid() const; [[nodiscard]] QString uuid() const;
[[nodiscard]] QString path() const; [[nodiscard]] QString path() const;
void setPath(const QString& path); void setPath(const QString& path);
[[nodiscard]] QObjectList entries() const; [[nodiscard]] QObjectList entries() const;
void setEntries(const QObjectList& entries); void setEntries(const QObjectList& entries);
[[nodiscard]] QQmlListProperty<AppEntry> apps(); [[nodiscard]] QQmlListProperty<AppEntry> apps();
Q_INVOKABLE void incrementFrequency(const QString& id); Q_INVOKABLE void incrementFrequency(const QString& id);
signals: signals:
void pathChanged(); void pathChanged();
void entriesChanged(); void entriesChanged();
void appsChanged(); void appsChanged();
private: private:
QTimer* m_timer; QTimer* m_timer;
const QString m_uuid; const QString m_uuid;
QString m_path; QString m_path;
QObjectList m_entries; QObjectList m_entries;
QHash<QString, AppEntry*> m_apps; QHash<QString, AppEntry*> m_apps;
mutable QList<AppEntry*> m_sortedApps; mutable QList<AppEntry*> m_sortedApps;
QList<AppEntry*>& getSortedApps() const; QList<AppEntry*>& getSortedApps() const;
quint32 getFrequency(const QString& id) const; quint32 getFrequency(const QString& id) const;
void updateAppFrequencies(); void updateAppFrequencies();
void updateApps(); void updateApps();
}; };
} // namespace ZShell } // namespace ZShell
+190 -150
View File
@@ -9,215 +9,255 @@
namespace ZShell { namespace ZShell {
ImageAnalyser::ImageAnalyser(QObject* parent) ImageAnalyser::ImageAnalyser(QObject* parent)
: QObject(parent) : QObject(parent)
, m_futureWatcher(new QFutureWatcher<AnalyseResult>(this)) , m_futureWatcher(new QFutureWatcher<AnalyseResult>(this))
, m_source("") , m_source("")
, m_sourceItem(nullptr) , m_sourceItem(nullptr)
, m_rescaleSize(128) , m_rescaleSize(128)
, m_dominantColour(0, 0, 0) , m_dominantColour(0, 0, 0)
, m_luminance(0) { , m_luminance(0) {
QObject::connect(m_futureWatcher, &QFutureWatcher<AnalyseResult>::finished, this, [this]() { QObject::connect(
if (!m_futureWatcher->future().isResultReadyAt(0)) { m_futureWatcher,
return; &QFutureWatcher<AnalyseResult>::finished,
} this,
[this]() {
if (!m_futureWatcher->future().isResultReadyAt(0)) {
return;
}
const auto result = m_futureWatcher->result(); const auto result = m_futureWatcher->result();
if (m_dominantColour != result.first) { if (m_dominantColour != result.first) {
m_dominantColour = result.first; m_dominantColour = result.first;
emit dominantColourChanged(); emit dominantColourChanged();
} }
if (!qFuzzyCompare(m_luminance + 1.0, result.second + 1.0)) { if (!qFuzzyCompare(m_luminance + 1.0, result.second + 1.0)) {
m_luminance = result.second; m_luminance = result.second;
emit luminanceChanged(); emit luminanceChanged();
} }
}); });
} }
QString ImageAnalyser::source() const { QString ImageAnalyser::source() const {
return m_source; return m_source;
} }
void ImageAnalyser::setSource(const QString& source) { void ImageAnalyser::setSource(const QString& source) {
if (m_source == source) { if (m_source == source) {
return; return;
} }
m_source = source; m_source = source;
emit sourceChanged(); emit sourceChanged();
if (m_sourceItem) { if (m_sourceItem) {
m_sourceItem = nullptr; m_sourceItem = nullptr;
emit sourceItemChanged(); emit sourceItemChanged();
} }
requestUpdate(); requestUpdate();
} }
QQuickItem* ImageAnalyser::sourceItem() const { QQuickItem* ImageAnalyser::sourceItem() const {
return m_sourceItem; return m_sourceItem;
} }
void ImageAnalyser::setSourceItem(QQuickItem* sourceItem) { void ImageAnalyser::setSourceItem(QQuickItem* sourceItem) {
if (m_sourceItem == sourceItem) { if (m_sourceItem == sourceItem) {
return; return;
} }
m_sourceItem = sourceItem; m_sourceItem = sourceItem;
emit sourceItemChanged(); emit sourceItemChanged();
if (!m_source.isEmpty()) { if (!m_source.isEmpty()) {
m_source = ""; m_source = "";
emit sourceChanged(); emit sourceChanged();
} }
requestUpdate(); requestUpdate();
} }
int ImageAnalyser::rescaleSize() const { int ImageAnalyser::rescaleSize() const {
return m_rescaleSize; return m_rescaleSize;
} }
void ImageAnalyser::setRescaleSize(int rescaleSize) { void ImageAnalyser::setRescaleSize(int rescaleSize) {
if (m_rescaleSize == rescaleSize) { if (m_rescaleSize == rescaleSize) {
return; return;
} }
m_rescaleSize = rescaleSize; m_rescaleSize = rescaleSize;
emit rescaleSizeChanged(); emit rescaleSizeChanged();
requestUpdate(); requestUpdate();
} }
QColor ImageAnalyser::dominantColour() const { QColor ImageAnalyser::dominantColour() const {
return m_dominantColour; return m_dominantColour;
} }
qreal ImageAnalyser::luminance() const { qreal ImageAnalyser::luminance() const {
return m_luminance; return m_luminance;
} }
void ImageAnalyser::requestUpdate() { void ImageAnalyser::requestUpdate() {
if (m_source.isEmpty() && !m_sourceItem) { if (m_source.isEmpty() && !m_sourceItem) {
return; return;
} }
if (!m_sourceItem || (m_sourceItem->window() && m_sourceItem->window()->isVisible() && m_sourceItem->width() > 0 && if (!m_sourceItem ||
m_sourceItem->height() > 0)) { (m_sourceItem->window() && m_sourceItem->window()->isVisible() &&
update(); m_sourceItem->width() > 0 && m_sourceItem->height() > 0)) {
} else if (m_sourceItem) { update();
if (!m_sourceItem->window()) { } else if (m_sourceItem) {
QObject::connect(m_sourceItem, &QQuickItem::windowChanged, this, &ImageAnalyser::requestUpdate, if (!m_sourceItem->window()) {
Qt::SingleShotConnection); QObject::connect(
} else if (!m_sourceItem->window()->isVisible()) { m_sourceItem,
QObject::connect(m_sourceItem->window(), &QQuickWindow::visibleChanged, this, &ImageAnalyser::requestUpdate, &QQuickItem::windowChanged,
Qt::SingleShotConnection); this,
} &ImageAnalyser::requestUpdate,
if (m_sourceItem->width() <= 0) { Qt::SingleShotConnection);
QObject::connect( } else if (!m_sourceItem->window()->isVisible()) {
m_sourceItem, &QQuickItem::widthChanged, this, &ImageAnalyser::requestUpdate, Qt::SingleShotConnection); QObject::connect(
} m_sourceItem->window(),
if (m_sourceItem->height() <= 0) { &QQuickWindow::visibleChanged,
QObject::connect(m_sourceItem, &QQuickItem::heightChanged, this, &ImageAnalyser::requestUpdate, this,
Qt::SingleShotConnection); &ImageAnalyser::requestUpdate,
} Qt::SingleShotConnection);
} }
if (m_sourceItem->width() <= 0) {
QObject::connect(
m_sourceItem,
&QQuickItem::widthChanged,
this,
&ImageAnalyser::requestUpdate,
Qt::SingleShotConnection);
}
if (m_sourceItem->height() <= 0) {
QObject::connect(
m_sourceItem,
&QQuickItem::heightChanged,
this,
&ImageAnalyser::requestUpdate,
Qt::SingleShotConnection);
}
}
} }
void ImageAnalyser::update() { void ImageAnalyser::update() {
if (m_source.isEmpty() && !m_sourceItem) { if (m_source.isEmpty() && !m_sourceItem) {
return; return;
} }
if (m_futureWatcher->isRunning()) { if (m_futureWatcher->isRunning()) {
m_futureWatcher->cancel(); m_futureWatcher->cancel();
} }
if (m_sourceItem) { if (m_sourceItem) {
const QSharedPointer<const QQuickItemGrabResult> grabResult = m_sourceItem->grabToImage(); const QSharedPointer<const QQuickItemGrabResult> grabResult =
QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this, [grabResult, this]() { m_sourceItem->grabToImage();
m_futureWatcher->setFuture(QtConcurrent::run(&ImageAnalyser::analyse, grabResult->image(), m_rescaleSize)); QObject::connect(
}); grabResult.data(),
} else { &QQuickItemGrabResult::ready,
m_futureWatcher->setFuture(QtConcurrent::run([=, this](QPromise<AnalyseResult>& promise) { this,
const QImage image(m_source); [grabResult, this]() {
analyse(promise, image, m_rescaleSize); m_futureWatcher->setFuture(
})); QtConcurrent::run(
} &ImageAnalyser::analyse,
grabResult->image(),
m_rescaleSize));
});
} else {
m_futureWatcher->setFuture(
QtConcurrent::run([=, this](QPromise<AnalyseResult>& promise) {
const QImage image(m_source);
analyse(promise, image, m_rescaleSize);
}));
}
} }
void ImageAnalyser::analyse(QPromise<AnalyseResult>& promise, const QImage& image, int rescaleSize) { void ImageAnalyser::analyse(
if (image.isNull()) { QPromise<AnalyseResult>& promise, const QImage& image, int rescaleSize) {
qWarning() << "ImageAnalyser::analyse: image is null"; if (image.isNull()) {
return; qWarning() << "ImageAnalyser::analyse: image is null";
} return;
}
QImage img = image; QImage img = image;
if (rescaleSize > 0 && (img.width() > rescaleSize || img.height() > rescaleSize)) { if (rescaleSize > 0 &&
img = img.scaled(rescaleSize, rescaleSize, Qt::KeepAspectRatio, Qt::FastTransformation); (img.width() > rescaleSize || img.height() > rescaleSize)) {
} img = img.scaled(
rescaleSize,
rescaleSize,
Qt::KeepAspectRatio,
Qt::FastTransformation);
}
if (promise.isCanceled()) { if (promise.isCanceled()) {
return; return;
} }
if (img.format() != QImage::Format_ARGB32) { if (img.format() != QImage::Format_ARGB32) {
img = img.convertToFormat(QImage::Format_ARGB32); img = img.convertToFormat(QImage::Format_ARGB32);
} }
if (promise.isCanceled()) { if (promise.isCanceled()) {
return; return;
} }
const uchar* data = img.bits(); const uchar* data = img.bits();
const int width = img.width(); const int width = img.width();
const int height = img.height(); const int height = img.height();
const qsizetype bytesPerLine = img.bytesPerLine(); const qsizetype bytesPerLine = img.bytesPerLine();
std::unordered_map<quint32, int> colours; std::unordered_map<quint32, int> colours;
qreal totalLuminance = 0.0; qreal totalLuminance = 0.0;
int count = 0; int count = 0;
for (int y = 0; y < height; ++y) { for (int y = 0; y < height; ++y) {
const uchar* line = data + y * bytesPerLine; const uchar* line = data + y * bytesPerLine;
for (int x = 0; x < width; ++x) { for (int x = 0; x < width; ++x) {
if (promise.isCanceled()) { if (promise.isCanceled()) {
return; return;
} }
const uchar* pixel = line + x * 4; const uchar* pixel = line + x * 4;
if (pixel[3] == 0) { if (pixel[3] == 0) {
continue; continue;
} }
const quint32 mr = static_cast<quint32>(pixel[0] & 0xF8); const quint32 mr = static_cast<quint32>(pixel[0] & 0xF8);
const quint32 mg = static_cast<quint32>(pixel[1] & 0xF8); const quint32 mg = static_cast<quint32>(pixel[1] & 0xF8);
const quint32 mb = static_cast<quint32>(pixel[2] & 0xF8); const quint32 mb = static_cast<quint32>(pixel[2] & 0xF8);
++colours[(mr << 16) | (mg << 8) | mb]; ++colours[(mr << 16) | (mg << 8) | mb];
const qreal r = pixel[0] / 255.0; const qreal r = pixel[0] / 255.0;
const qreal g = pixel[1] / 255.0; const qreal g = pixel[1] / 255.0;
const qreal b = pixel[2] / 255.0; const qreal b = pixel[2] / 255.0;
totalLuminance += std::sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b); totalLuminance +=
++count; std::sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b);
} ++count;
} }
}
quint32 dominantColour = 0; quint32 dominantColour = 0;
int maxCount = 0; int maxCount = 0;
for (const auto& [colour, colourCount] : colours) { for (const auto& [colour, colourCount] : colours) {
if (promise.isCanceled()) { if (promise.isCanceled()) {
return; return;
} }
if (colourCount > maxCount) { if (colourCount > maxCount) {
dominantColour = colour; dominantColour = colour;
maxCount = colourCount; maxCount = colourCount;
} }
} }
promise.addResult(qMakePair(QColor((0xFFu << 24) | dominantColour), count == 0 ? 0.0 : totalLuminance / count)); promise.addResult(qMakePair(
QColor((0xFFu << 24) | dominantColour),
count == 0 ? 0.0 : totalLuminance / count));
} }
} // namespace ZShell } // namespace ZShell
+40 -34
View File
@@ -9,53 +9,59 @@
namespace ZShell { namespace ZShell {
class ImageAnalyser : public QObject { class ImageAnalyser : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged) Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(QQuickItem* sourceItem READ sourceItem WRITE setSourceItem NOTIFY sourceItemChanged) Q_PROPERTY(
Q_PROPERTY(int rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged) QQuickItem* sourceItem READ sourceItem WRITE setSourceItem NOTIFY
Q_PROPERTY(QColor dominantColour READ dominantColour NOTIFY dominantColourChanged) sourceItemChanged)
Q_PROPERTY(qreal luminance READ luminance NOTIFY luminanceChanged) Q_PROPERTY(
int rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY
rescaleSizeChanged)
Q_PROPERTY(
QColor dominantColour READ dominantColour NOTIFY dominantColourChanged)
Q_PROPERTY(qreal luminance READ luminance NOTIFY luminanceChanged)
public: public:
explicit ImageAnalyser(QObject* parent = nullptr); explicit ImageAnalyser(QObject* parent = nullptr);
[[nodiscard]] QString source() const; [[nodiscard]] QString source() const;
void setSource(const QString& source); void setSource(const QString& source);
[[nodiscard]] QQuickItem* sourceItem() const; [[nodiscard]] QQuickItem* sourceItem() const;
void setSourceItem(QQuickItem* sourceItem); void setSourceItem(QQuickItem* sourceItem);
[[nodiscard]] int rescaleSize() const; [[nodiscard]] int rescaleSize() const;
void setRescaleSize(int rescaleSize); void setRescaleSize(int rescaleSize);
[[nodiscard]] QColor dominantColour() const; [[nodiscard]] QColor dominantColour() const;
[[nodiscard]] qreal luminance() const; [[nodiscard]] qreal luminance() const;
Q_INVOKABLE void requestUpdate(); Q_INVOKABLE void requestUpdate();
signals: signals:
void sourceChanged(); void sourceChanged();
void sourceItemChanged(); void sourceItemChanged();
void rescaleSizeChanged(); void rescaleSizeChanged();
void dominantColourChanged(); void dominantColourChanged();
void luminanceChanged(); void luminanceChanged();
private: private:
using AnalyseResult = QPair<QColor, qreal>; using AnalyseResult = QPair<QColor, qreal>;
QFutureWatcher<AnalyseResult>* const m_futureWatcher; QFutureWatcher<AnalyseResult>* const m_futureWatcher;
QString m_source; QString m_source;
QQuickItem* m_sourceItem; QQuickItem* m_sourceItem;
int m_rescaleSize; int m_rescaleSize;
QColor m_dominantColour; QColor m_dominantColour;
qreal m_luminance; qreal m_luminance;
void update(); void update();
static void analyse(QPromise<AnalyseResult>& promise, const QImage& image, int rescaleSize); static void analyse(
QPromise<AnalyseResult>& promise, const QImage& image, int rescaleSize);
}; };
} // namespace ZShell } // namespace ZShell
+6 -3
View File
@@ -4,8 +4,7 @@
namespace ZShell { namespace ZShell {
Qalculator::Qalculator(QObject* parent) Qalculator::Qalculator(QObject* parent) : QObject(parent) {
: QObject(parent) {
if (!CALCULATOR) { if (!CALCULATOR) {
new Calculator(); new Calculator();
CALCULATOR->loadExchangeRates(); CALCULATOR->loadExchangeRates();
@@ -24,7 +23,11 @@ QString Qalculator::eval(const QString& expr, bool printExpr) const {
std::string parsed; std::string parsed;
std::string result = CALCULATOR->calculateAndPrint( std::string result = CALCULATOR->calculateAndPrint(
CALCULATOR->unlocalizeExpression(expr.toStdString(), eo.parse_options), 100, eo, po, &parsed); CALCULATOR->unlocalizeExpression(expr.toStdString(), eo.parse_options),
100,
eo,
po,
&parsed);
std::string error; std::string error;
while (CALCULATOR->message()) { while (CALCULATOR->message()) {
+7 -6
View File
@@ -6,14 +6,15 @@
namespace ZShell { namespace ZShell {
class Qalculator : public QObject { class Qalculator : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_SINGLETON QML_SINGLETON
public: public:
explicit Qalculator(QObject* parent = nullptr); explicit Qalculator(QObject* parent = nullptr);
Q_INVOKABLE [[nodiscard]] QString eval(const QString& expr, bool printExpr = true) const; Q_INVOKABLE [[nodiscard]] QString eval(
const QString& expr, bool printExpr = true) const;
}; };
} // namespace ZShell } // namespace ZShell
+7 -7
View File
@@ -7,9 +7,7 @@
namespace ZShell { namespace ZShell {
Requests::Requests(QObject* parent) Requests::Requests(QObject* parent)
: QObject(parent) : QObject(parent), m_manager(new QNetworkAccessManager(this)) {}
, m_manager(new QNetworkAccessManager(this)) {
}
void Requests::get(const QUrl& url, QJSValue onSuccess, QJSValue onError) const { void Requests::get(const QUrl& url, QJSValue onSuccess, QJSValue onError) const {
if (!onSuccess.isCallable()) { if (!onSuccess.isCallable()) {
@@ -20,13 +18,15 @@ void Requests::get(const QUrl& url, QJSValue onSuccess, QJSValue onError) const
QNetworkRequest request(url); QNetworkRequest request(url);
auto reply = m_manager->get(request); auto reply = m_manager->get(request);
QObject::connect(reply, &QNetworkReply::finished, [reply, onSuccess, onError]() { QObject::connect(
reply, &QNetworkReply::finished, [reply, onSuccess, onError]() {
if (reply->error() == QNetworkReply::NoError) { if (reply->error() == QNetworkReply::NoError) {
onSuccess.call({ QString(reply->readAll()) }); onSuccess.call({QString(reply->readAll())});
} else if (onError.isCallable()) { } else if (onError.isCallable()) {
onError.call({ reply->errorString() }); onError.call({reply->errorString()});
} else { } else {
qWarning() << "Requests::get: request failed with error" << reply->errorString(); qWarning() << "Requests::get: request failed with error"
<< reply->errorString();
} }
reply->deleteLater(); reply->deleteLater();
+11 -8
View File
@@ -7,17 +7,20 @@
namespace ZShell { namespace ZShell {
class Requests : public QObject { class Requests : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_SINGLETON QML_SINGLETON
public: public:
explicit Requests(QObject* parent = nullptr); explicit Requests(QObject* parent = nullptr);
Q_INVOKABLE void get(const QUrl& url, QJSValue callback, QJSValue onError = QJSValue()) const; Q_INVOKABLE void get(
const QUrl& url,
QJSValue callback,
QJSValue onError = QJSValue()) const;
private: private:
QNetworkAccessManager* m_manager; QNetworkAccessManager* m_manager;
}; };
} // namespace ZShell } // namespace ZShell
+19 -10
View File
@@ -6,7 +6,13 @@
namespace ZShell { namespace ZShell {
Toast::Toast(const QString& title, const QString& message, const QString& icon, Type type, int timeout, QObject* parent) Toast::Toast(
const QString& title,
const QString& message,
const QString& icon,
Type type,
int timeout,
QObject* parent)
: QObject(parent) : QObject(parent)
, m_closed(false) , m_closed(false)
, m_title(title) , m_title(title)
@@ -94,22 +100,25 @@ void Toast::unlock(QObject* sender) {
} }
} }
Toaster::Toaster(QObject* parent) Toaster::Toaster(QObject* parent) : QObject(parent) {}
: QObject(parent) {
}
QQmlListProperty<Toast> Toaster::toasts() { QQmlListProperty<Toast> Toaster::toasts() {
return QQmlListProperty<Toast>(this, &m_toasts); return QQmlListProperty<Toast>(this, &m_toasts);
} }
void Toaster::toast(const QString& title, const QString& message, const QString& icon, Toast::Type type, int timeout) { void Toaster::toast(
const QString& title,
const QString& message,
const QString& icon,
Toast::Type type,
int timeout) {
auto* toast = new Toast(title, message, icon, type, timeout, this); auto* toast = new Toast(title, message, icon, type, timeout, this);
QObject::connect(toast, &Toast::finishedClose, this, [toast, this]() { QObject::connect(toast, &Toast::finishedClose, this, [toast, this]() {
if (m_toasts.removeOne(toast)) { if (m_toasts.removeOne(toast)) {
emit toastsChanged(); emit toastsChanged();
toast->deleteLater(); toast->deleteLater();
} }
}); });
m_toasts.push_front(toast); m_toasts.push_front(toast);
emit toastsChanged(); emit toastsChanged();
} }
+57 -52
View File
@@ -8,75 +8,80 @@
namespace ZShell { namespace ZShell {
class Toast : public QObject { class Toast : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("Toast instances can only be retrieved from a Toaster") QML_UNCREATABLE("Toast instances can only be retrieved from a Toaster")
Q_PROPERTY(bool closed READ closed NOTIFY closedChanged) Q_PROPERTY(bool closed READ closed NOTIFY closedChanged)
Q_PROPERTY(QString title READ title CONSTANT) Q_PROPERTY(QString title READ title CONSTANT)
Q_PROPERTY(QString message READ message CONSTANT) Q_PROPERTY(QString message READ message CONSTANT)
Q_PROPERTY(QString icon READ icon CONSTANT) Q_PROPERTY(QString icon READ icon CONSTANT)
Q_PROPERTY(int timeout READ timeout CONSTANT) Q_PROPERTY(int timeout READ timeout CONSTANT)
Q_PROPERTY(Type type READ type CONSTANT) Q_PROPERTY(Type type READ type CONSTANT)
public: public:
enum class Type { enum class Type { Info = 0, Success, Warning, Error };
Info = 0, Q_ENUM(Type)
Success,
Warning,
Error
};
Q_ENUM(Type)
explicit Toast(const QString& title, const QString& message, const QString& icon, Type type, int timeout, explicit Toast(
QObject* parent = nullptr); const QString& title,
const QString& message,
const QString& icon,
Type type,
int timeout,
QObject* parent = nullptr);
[[nodiscard]] bool closed() const; [[nodiscard]] bool closed() const;
[[nodiscard]] QString title() const; [[nodiscard]] QString title() const;
[[nodiscard]] QString message() const; [[nodiscard]] QString message() const;
[[nodiscard]] QString icon() const; [[nodiscard]] QString icon() const;
[[nodiscard]] int timeout() const; [[nodiscard]] int timeout() const;
[[nodiscard]] Type type() const; [[nodiscard]] Type type() const;
Q_INVOKABLE void close(); Q_INVOKABLE void close();
Q_INVOKABLE void lock(QObject* sender); Q_INVOKABLE void lock(QObject* sender);
Q_INVOKABLE void unlock(QObject* sender); Q_INVOKABLE void unlock(QObject* sender);
signals: signals:
void closedChanged(); void closedChanged();
void finishedClose(); void finishedClose();
private: private:
QSet<QObject*> m_locks; QSet<QObject*> m_locks;
bool m_closed; bool m_closed;
QString m_title; QString m_title;
QString m_message; QString m_message;
QString m_icon; QString m_icon;
Type m_type; Type m_type;
int m_timeout; int m_timeout;
}; };
class Toaster : public QObject { class Toaster : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_SINGLETON QML_SINGLETON
Q_PROPERTY(QQmlListProperty<ZShell::Toast> toasts READ toasts NOTIFY toastsChanged) Q_PROPERTY(
QQmlListProperty<ZShell::Toast> toasts READ toasts NOTIFY toastsChanged)
public: public:
explicit Toaster(QObject* parent = nullptr); explicit Toaster(QObject* parent = nullptr);
[[nodiscard]] QQmlListProperty<Toast> toasts(); [[nodiscard]] QQmlListProperty<Toast> toasts();
Q_INVOKABLE void toast(const QString& title, const QString& message, const QString& icon = QString(), Q_INVOKABLE void toast(
ZShell::Toast::Type type = Toast::Type::Info, int timeout = 5000); const QString& title,
const QString& message,
const QString& icon = QString(),
ZShell::Toast::Type type = Toast::Type::Info,
int timeout = 5000);
signals: signals:
void toastsChanged(); void toastsChanged();
private: private:
QList<Toast*> m_toasts; QList<Toast*> m_toasts;
}; };
} // namespace ZShell } // namespace ZShell
+125 -137
View File
@@ -20,15 +20,12 @@
namespace ZShell { namespace ZShell {
// ============================================================
// saveItem
// ============================================================
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path) { void ZShellIo::saveItem(QQuickItem* target, const QUrl& path) {
this->saveItem(target, path, QRect(), QJSValue(), QJSValue()); this->saveItem(target, path, QRect(), QJSValue(), QJSValue());
} }
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect) { void ZShellIo::saveItem(
QQuickItem* target, const QUrl& path, const QRect& rect) {
this->saveItem(target, path, rect, QJSValue(), QJSValue()); this->saveItem(target, path, rect, QJSValue(), QJSValue());
} }
@@ -36,11 +33,13 @@ void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved)
this->saveItem(target, path, QRect(), onSaved, QJSValue()); this->saveItem(target, path, QRect(), onSaved, QJSValue());
} }
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed) { void ZShellIo::saveItem(
QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed) {
this->saveItem(target, path, QRect(), onSaved, onFailed); this->saveItem(target, path, QRect(), onSaved, onFailed);
} }
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved) { void ZShellIo::saveItem(
QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved) {
this->saveItem(target, path, rect, onSaved, QJSValue()); this->saveItem(target, path, rect, onSaved, QJSValue());
} }
@@ -49,8 +48,7 @@ void ZShellIo::saveItem(
const QUrl& path, const QUrl& path,
const QRect& rect, const QRect& rect,
QJSValue onSaved, QJSValue onSaved,
QJSValue onFailed QJSValue onFailed) {
) {
if (!target) { if (!target) {
qWarning() << "ZShellIo::saveItem: a target is required"; qWarning() << "ZShellIo::saveItem: a target is required";
return; return;
@@ -62,9 +60,8 @@ void ZShellIo::saveItem(
} }
if (!target->window()) { if (!target->window()) {
qWarning() << "ZShellIo::saveItem: unable to save target" qWarning() << "ZShellIo::saveItem: unable to save target" << target
<< target << "without a window";
<< "without a window";
return; return;
} }
@@ -74,11 +71,11 @@ void ZShellIo::saveItem(
if (rect.isValid() && !qFuzzyCompare(scale + 1.0, 2.0)) { if (rect.isValid() && !qFuzzyCompare(scale + 1.0, 2.0)) {
scaledRect = QRectF( scaledRect = QRectF(
rect.left() * scale, rect.left() * scale,
rect.top() * scale, rect.top() * scale,
rect.width() * scale, rect.width() * scale,
rect.height() * scale rect.height() * scale)
).toRect(); .toRect();
} }
const QSharedPointer<const QQuickItemGrabResult> grabResult = const QSharedPointer<const QQuickItemGrabResult> grabResult =
@@ -89,66 +86,62 @@ void ZShellIo::saveItem(
&QQuickItemGrabResult::ready, &QQuickItemGrabResult::ready,
this, this,
[grabResult, scaledRect, path, onSaved, onFailed, this]() { [grabResult, scaledRect, path, onSaved, onFailed, this]() {
const auto future = QtConcurrent::run([grabResult, scaledRect, path]() { const auto future =
QImage image = grabResult->image(); QtConcurrent::run([grabResult, scaledRect, path]() {
QImage image = grabResult->image();
if (scaledRect.isValid()) { if (scaledRect.isValid()) {
image = image.copy(scaledRect); image = image.copy(scaledRect);
} }
const QString file = path.toLocalFile(); const QString file = path.toLocalFile();
const QString parent = QFileInfo(file).absolutePath(); const QString parent = QFileInfo(file).absolutePath();
QDir().mkpath(parent); QDir().mkpath(parent);
QSaveFile out(file); QSaveFile out(file);
if (!out.open(QIODevice::WriteOnly)) { if (!out.open(QIODevice::WriteOnly)) {
return false; return false;
} }
if (!image.save(&out, "PNG")) { if (!image.save(&out, "PNG")) {
return false; return false;
} }
return out.commit(); return out.commit();
}); });
auto* watcher = new QFutureWatcher<bool>(this); auto* watcher = new QFutureWatcher<bool>(this);
auto* engine = qmlEngine(this); auto* engine = qmlEngine(this);
QObject::connect(watcher, &QFutureWatcher<bool>::finished, this, [=]() { QObject::connect(
if (watcher->result()) { watcher, &QFutureWatcher<bool>::finished, this, [=]() {
if (onSaved.isCallable() && engine) { if (watcher->result()) {
onSaved.call({ if (onSaved.isCallable() && engine) {
engine->toScriptValue(path.toLocalFile()), onSaved.call(
engine->toScriptValue(path) {engine->toScriptValue(path.toLocalFile()),
}); engine->toScriptValue(path)});
}
} else {
qWarning()
<< "ZShellIo::saveItem: failed to save" << path;
if (onFailed.isCallable() && engine) {
onFailed.call({engine->toScriptValue(path)});
}
} }
} else { watcher->deleteLater();
qWarning() << "ZShellIo::saveItem: failed to save" << path; });
if (onFailed.isCallable() && engine) {
onFailed.call({
engine->toScriptValue(path)
});
}
}
watcher->deleteLater();
});
watcher->setFuture(future); watcher->setFuture(future);
} });
);
} }
// ============================================================
// cacheImage
// ============================================================
void ZShellIo::cacheImage(const QUrl& source, const QString& cacheDir) { void ZShellIo::cacheImage(const QUrl& source, const QString& cacheDir) {
this->cacheImage(source, cacheDir, QJSValue(), QJSValue()); this->cacheImage(source, cacheDir, QJSValue(), QJSValue());
} }
void ZShellIo::cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved) { void ZShellIo::cacheImage(
const QUrl& source, const QString& cacheDir, QJSValue onSaved) {
this->cacheImage(source, cacheDir, onSaved, QJSValue()); this->cacheImage(source, cacheDir, onSaved, QJSValue());
} }
@@ -156,8 +149,7 @@ void ZShellIo::cacheImage(
const QUrl& source, const QUrl& source,
const QString& cacheDir, const QString& cacheDir,
QJSValue onSaved, QJSValue onSaved,
QJSValue onFailed QJSValue onFailed) {
) {
if (cacheDir.isEmpty()) { if (cacheDir.isEmpty()) {
qWarning() << "ZShellIo::cacheImage: cacheDir is empty"; qWarning() << "ZShellIo::cacheImage: cacheDir is empty";
return; return;
@@ -165,92 +157,87 @@ void ZShellIo::cacheImage(
QImage image; QImage image;
if (!loadSourceImage(source, image)) { if (!loadSourceImage(source, image)) {
qWarning() << "ZShellIo::cacheImage: failed to load source image" << source; qWarning() << "ZShellIo::cacheImage: failed to load source image"
<< source;
auto* engine = qmlEngine(this); auto* engine = qmlEngine(this);
if (onFailed.isCallable() && engine) { if (onFailed.isCallable() && engine) {
onFailed.call({ onFailed.call(
engine->toScriptValue(source), {engine->toScriptValue(source),
engine->toScriptValue(cacheDir) engine->toScriptValue(cacheDir)});
});
} }
return; return;
} }
const auto future = QtConcurrent::run([image, cacheDir]() -> QString { const auto future = QtConcurrent::run([image, cacheDir]() -> QString {
if (image.isNull()) { if (image.isNull()) {
return QString(); return QString();
} }
const QImage normalized = image.convertToFormat(QImage::Format_RGBA8888); const QImage normalized =
image.convertToFormat(QImage::Format_RGBA8888);
const QByteArray bytes( const QByteArray bytes(
reinterpret_cast<const char*>(normalized.constBits()), reinterpret_cast<const char*>(normalized.constBits()),
qsizetype(normalized.sizeInBytes()) normalized.sizeInBytes());
);
const QByteArray digest = const QByteArray digest =
QCryptographicHash::hash(bytes, QCryptographicHash::Sha256).toHex(); QCryptographicHash::hash(bytes, QCryptographicHash::Sha256).toHex();
QDir dir(cacheDir); QDir dir(cacheDir);
if (!dir.exists() && !QDir().mkpath(cacheDir)) { if (!dir.exists() && !QDir().mkpath(cacheDir)) {
return QString(); return QString();
} }
const QString finalPath = dir.filePath(QString::fromLatin1(digest) + ".png"); const QString finalPath =
dir.filePath(QString::fromLatin1(digest) + ".png");
if (QFile::exists(finalPath)) {
return finalPath;
}
QSaveFile out(finalPath);
if (!out.open(QIODevice::WriteOnly)) {
return QString();
}
if (!normalized.save(&out, "PNG")) {
return QString();
}
if (!out.commit()) {
return QString();
}
if (QFile::exists(finalPath)) {
return finalPath; return finalPath;
}); }
QSaveFile out(finalPath);
if (!out.open(QIODevice::WriteOnly)) {
return QString();
}
if (!normalized.save(&out, "PNG")) {
return QString();
}
if (!out.commit()) {
return QString();
}
return finalPath;
});
auto* watcher = new QFutureWatcher<QString>(this); auto* watcher = new QFutureWatcher<QString>(this);
auto* engine = qmlEngine(this); auto* engine = qmlEngine(this);
QObject::connect(watcher, &QFutureWatcher<QString>::finished, this, [=]() { QObject::connect(watcher, &QFutureWatcher<QString>::finished, this, [=]() {
const QString finalPath = watcher->result(); const QString finalPath = watcher->result();
if (!finalPath.isEmpty()) { if (!finalPath.isEmpty()) {
if (onSaved.isCallable() && engine) { if (onSaved.isCallable() && engine) {
onSaved.call({ onSaved.call(
engine->toScriptValue(finalPath), {engine->toScriptValue(finalPath),
engine->toScriptValue(QUrl::fromLocalFile(finalPath)) engine->toScriptValue(QUrl::fromLocalFile(finalPath))});
});
}
} else {
qWarning() << "ZShellIo::cacheImage: failed to cache" << source;
if (onFailed.isCallable() && engine) {
onFailed.call({
engine->toScriptValue(source),
engine->toScriptValue(cacheDir)
});
}
} }
} else {
qWarning() << "ZShellIo::cacheImage: failed to cache" << source;
if (onFailed.isCallable() && engine) {
onFailed.call(
{engine->toScriptValue(source),
engine->toScriptValue(cacheDir)});
}
}
watcher->deleteLater(); watcher->deleteLater();
}); });
watcher->setFuture(future); watcher->setFuture(future);
} }
// ============================================================
// loadSourceImage
// ============================================================
bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const { bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const {
image = QImage(); image = QImage();
@@ -270,22 +257,22 @@ bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const {
const QString providerId = source.host(); const QString providerId = source.host();
const QString imageId = const QString imageId = source.path().startsWith('/')
source.path().startsWith('/') ? source.path().mid(1)
? source.path().mid(1) : source.path();
: source.path();
auto* providerBase = engine->imageProvider(providerId); auto* providerBase = engine->imageProvider(providerId);
if (!providerBase) { if (!providerBase) {
qWarning() << "ZShellIo::loadSourceImage: provider not found" qWarning() << "ZShellIo::loadSourceImage: provider not found"
<< providerId; << providerId;
return false; return false;
} }
auto* provider = dynamic_cast<QQuickImageProvider*>(providerBase); auto* provider = dynamic_cast<QQuickImageProvider*>(providerBase);
if (!provider) { if (!provider) {
qWarning() << "ZShellIo::loadSourceImage: provider is not a QQuickImageProvider" qWarning() << "ZShellIo::loadSourceImage: provider is not a "
<< providerId; "QQuickImageProvider"
<< providerId;
return false; return false;
} }
@@ -302,7 +289,7 @@ bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const {
default: default:
qWarning() << "ZShellIo::loadSourceImage: unsupported provider type" qWarning() << "ZShellIo::loadSourceImage: unsupported provider type"
<< providerId; << providerId;
return false; return false;
} }
@@ -313,17 +300,16 @@ bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const {
return false; return false;
} }
// ============================================================ bool ZShellIo::copyFile(
// File ops const QUrl& source, const QUrl& target, bool overwrite) const {
// ============================================================
bool ZShellIo::copyFile(const QUrl& source, const QUrl& target, bool overwrite) const {
if (!source.isLocalFile()) { if (!source.isLocalFile()) {
qWarning() << "ZShellIo::copyFile: source" << source << "is not a local file"; qWarning() << "ZShellIo::copyFile: source" << source
<< "is not a local file";
return false; return false;
} }
if (!target.isLocalFile()) { if (!target.isLocalFile()) {
qWarning() << "ZShellIo::copyFile: target" << target << "is not a local file"; qWarning() << "ZShellIo::copyFile: target" << target
<< "is not a local file";
return false; return false;
} }
@@ -336,7 +322,8 @@ bool ZShellIo::copyFile(const QUrl& source, const QUrl& target, bool overwrite)
bool ZShellIo::deleteFile(const QUrl& path) const { bool ZShellIo::deleteFile(const QUrl& path) const {
if (!path.isLocalFile()) { if (!path.isLocalFile()) {
qWarning() << "ZShellIo::deleteFile: path" << path << "is not a local file"; qWarning() << "ZShellIo::deleteFile: path" << path
<< "is not a local file";
return false; return false;
} }
@@ -345,7 +332,8 @@ bool ZShellIo::deleteFile(const QUrl& path) const {
QString ZShellIo::toLocalFile(const QUrl& url) const { QString ZShellIo::toLocalFile(const QUrl& url) const {
if (!url.isLocalFile()) { if (!url.isLocalFile()) {
qWarning() << "ZShellIo::toLocalFile: given url is not a local file" << url; qWarning() << "ZShellIo::toLocalFile: given url is not a local file"
<< url;
return QString(); return QString();
} }
+21 -21
View File
@@ -10,31 +10,31 @@
namespace ZShell { namespace ZShell {
class ZShellIo : public QObject { class ZShellIo : public QObject {
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
Q_OBJECT public:
QML_ELEMENT // clang-format off
QML_SINGLETON Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
public: Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir);
// clang-format off Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path); Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved, QJSValue onFailed);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect); // clang-format on
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir); Q_INVOKABLE [[nodiscard]] bool copyFile(
Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved); const QUrl& source, const QUrl& target, bool overwrite = true) const;
Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved, QJSValue onFailed); Q_INVOKABLE [[nodiscard]] bool deleteFile(const QUrl& path) const;
// clang-format on Q_INVOKABLE [[nodiscard]] QString toLocalFile(const QUrl& url) const;
Q_INVOKABLE [[nodiscard]] bool copyFile(const QUrl& source, const QUrl& target, bool overwrite = true) const; private:
Q_INVOKABLE [[nodiscard]] bool deleteFile(const QUrl& path) const; bool loadSourceImage(const QUrl& source, QImage& image) const;
Q_INVOKABLE [[nodiscard]] QString toLocalFile(const QUrl& url) const;
private:
bool loadSourceImage(const QUrl& source, QImage& image) const;
}; };
} // namespace ZShell } // namespace ZShell
+72 -32
View File
@@ -3,9 +3,11 @@
#include <QtConcurrent/qtconcurrentrun.h> #include <QtConcurrent/qtconcurrentrun.h>
#include <QtQuick/qquickitemgrabresult.h> #include <QtQuick/qquickitemgrabresult.h>
#include <QtQuick/qquickwindow.h> #include <QtQuick/qquickwindow.h>
#include <qcontainerfwd.h>
#include <qdir.h> #include <qdir.h>
#include <qfileinfo.h> #include <qfileinfo.h>
#include <qfuturewatcher.h> #include <qfuturewatcher.h>
#include <qjsprimitivevalue.h>
#include <qloggingcategory.h> #include <qloggingcategory.h>
#include <qqmlengine.h> #include <qqmlengine.h>
@@ -25,15 +27,22 @@ void ZUtils::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved) {
this->saveItem(target, path, QRect(), onSaved, QJSValue()); this->saveItem(target, path, QRect(), onSaved, QJSValue());
} }
void ZUtils::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed) { void ZUtils::saveItem(
QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed) {
this->saveItem(target, path, QRect(), onSaved, onFailed); this->saveItem(target, path, QRect(), onSaved, onFailed);
} }
void ZUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved) { void ZUtils::saveItem(
QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved) {
this->saveItem(target, path, rect, onSaved, QJSValue()); this->saveItem(target, path, rect, onSaved, QJSValue());
} }
void ZUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed) { void ZUtils::saveItem(
QQuickItem* target,
const QUrl& path,
const QRect& rect,
QJSValue onSaved,
QJSValue onFailed) {
if (!target) { if (!target) {
qCWarning(lcZUtils) << "saveItem: a target is required"; qCWarning(lcZUtils) << "saveItem: a target is required";
return; return;
@@ -45,21 +54,30 @@ void ZUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, Q
} }
if (!target->window()) { if (!target->window()) {
qCWarning(lcZUtils) << "saveItem: unable to save target" << target << "without a window"; qCWarning(lcZUtils) << "saveItem: unable to save target" << target
<< "without a window";
return; return;
} }
auto scaledRect = rect; auto scaledRect = rect;
const qreal scale = target->window()->devicePixelRatio(); const qreal scale = target->window()->devicePixelRatio();
if (rect.isValid() && !qFuzzyCompare(scale + 1.0, 2.0)) { if (rect.isValid() && !qFuzzyCompare(scale + 1.0, 2.0)) {
scaledRect = scaledRect = QRectF(
QRectF(rect.left() * scale, rect.top() * scale, rect.width() * scale, rect.height() * scale).toRect(); rect.left() * scale,
rect.top() * scale,
rect.width() * scale,
rect.height() * scale)
.toRect();
} }
const QSharedPointer<const QQuickItemGrabResult> grabResult = target->grabToImage(); const QSharedPointer<const QQuickItemGrabResult> grabResult =
target->grabToImage();
QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this, QObject::connect(
[grabResult, scaledRect, path, onSaved, onFailed, this]() { grabResult.data(),
&QQuickItemGrabResult::ready,
this,
[grabResult, scaledRect, path, onSaved, onFailed, this]() {
const auto future = QtConcurrent::run([=]() { const auto future = QtConcurrent::run([=]() {
QImage image = grabResult->image(); QImage image = grabResult->image();
@@ -75,44 +93,52 @@ void ZUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, Q
auto* watcher = new QFutureWatcher<bool>(this); auto* watcher = new QFutureWatcher<bool>(this);
auto* engine = qmlEngine(this); auto* engine = qmlEngine(this);
QObject::connect(watcher, &QFutureWatcher<bool>::finished, this, [=]() { QObject::connect(
if (watcher->result()) { watcher, &QFutureWatcher<bool>::finished, this, [=]() {
if (onSaved.isCallable()) { if (watcher->result()) {
QJSValueList args = { QJSValue(path.toLocalFile()) }; if (onSaved.isCallable()) {
if (engine) { QJSValueList args = {QJSValue(path.toLocalFile())};
args << engine->toScriptValue(QVariant::fromValue(path)); if (engine) {
args << engine->toScriptValue(
QVariant::fromValue(path));
}
onSaved.call(args);
} }
onSaved.call(args); } else {
} qCWarning(lcZUtils)
} else { << "saveItem: failed to save" << path;
qCWarning(lcZUtils) << "saveItem: failed to save" << path; if (onFailed.isCallable()) {
if (onFailed.isCallable()) { if (engine) {
if (engine) { onFailed.call({engine->toScriptValue(
onFailed.call({ engine->toScriptValue(QVariant::fromValue(path)) }); QVariant::fromValue(path))});
} else { } else {
onFailed.call(); onFailed.call();
}
} }
} }
} watcher->deleteLater();
watcher->deleteLater(); });
});
watcher->setFuture(future); watcher->setFuture(future);
}); });
} }
bool ZUtils::copyFile(const QUrl& source, const QUrl& target, bool overwrite) { bool ZUtils::copyFile(const QUrl& source, const QUrl& target, bool overwrite) {
if (!source.isLocalFile()) { if (!source.isLocalFile()) {
qCWarning(lcZUtils) << "copyFile: source" << source << "is not a local file"; qCWarning(lcZUtils)
<< "copyFile: source" << source << "is not a local file";
return false; return false;
} }
if (!target.isLocalFile()) { if (!target.isLocalFile()) {
qCWarning(lcZUtils) << "copyFile: target" << target << "is not a local file"; qCWarning(lcZUtils)
<< "copyFile: target" << target << "is not a local file";
return false; return false;
} }
if (overwrite && QFile::exists(target.toLocalFile())) { if (overwrite && QFile::exists(target.toLocalFile())) {
if (!QFile::remove(target.toLocalFile())) { if (!QFile::remove(target.toLocalFile())) {
qCWarning(lcZUtils) << "copyFile: overwrite was specified but failed to remove" << target.toLocalFile(); qCWarning(lcZUtils)
<< "copyFile: overwrite was specified but failed to remove"
<< target.toLocalFile();
return false; return false;
} }
} }
@@ -122,7 +148,8 @@ bool ZUtils::copyFile(const QUrl& source, const QUrl& target, bool overwrite) {
bool ZUtils::deleteFile(const QUrl& path) { bool ZUtils::deleteFile(const QUrl& path) {
if (!path.isLocalFile()) { if (!path.isLocalFile()) {
qCWarning(lcZUtils) << "deleteFile: path" << path << "is not a local file"; qCWarning(lcZUtils)
<< "deleteFile: path" << path << "is not a local file";
return false; return false;
} }
@@ -131,7 +158,8 @@ bool ZUtils::deleteFile(const QUrl& path) {
QString ZUtils::toLocalFile(const QUrl& url) { QString ZUtils::toLocalFile(const QUrl& url) {
if (!url.isLocalFile()) { if (!url.isLocalFile()) {
qCWarning(lcZUtils) << "toLocalFile: given url is not a local file" << url; qCWarning(lcZUtils)
<< "toLocalFile: given url is not a local file" << url;
return QString(); return QString();
} }
@@ -142,4 +170,16 @@ qreal ZUtils::clamp(qreal value, qreal min, qreal max) {
return qBound(min, value, max); return qBound(min, value, max);
} }
#ifndef ZSHELL_VERSION
#define ZSHELL_VERSION ""
#endif
QString ZUtils::version() const {
return QStringLiteral(ZSHELL_VERSION);
}
QString ZUtils::qtVersion() const {
return QStringLiteral(QT_VERSION_STR);
}
} // namespace ZShell } // namespace ZShell
+21 -16
View File
@@ -1,31 +1,36 @@
#pragma once #pragma once
#include <QtQuick/qquickitem.h> #include <QtQuick/qquickitem.h>
#include <qcontainerfwd.h>
#include <qobject.h> #include <qobject.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
namespace ZShell { namespace ZShell {
class ZUtils : public QObject { class ZUtils : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_SINGLETON QML_SINGLETON
public: public:
// clang-format off // clang-format off
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
// clang-format on // clang-format on
Q_INVOKABLE static bool copyFile(const QUrl& source, const QUrl& target, bool overwrite = true); Q_INVOKABLE static bool copyFile(
Q_INVOKABLE static bool deleteFile(const QUrl& path); const QUrl& source, const QUrl& target, bool overwrite = true);
Q_INVOKABLE static QString toLocalFile(const QUrl& url); Q_INVOKABLE static bool deleteFile(const QUrl& path);
Q_INVOKABLE static QString toLocalFile(const QUrl& url);
Q_INVOKABLE static qreal clamp(qreal value, qreal min, qreal max); Q_INVOKABLE static qreal clamp(qreal value, qreal min, qreal max);
[[nodiscard]] QString version() const;
[[nodiscard]] QString qtVersion() const;
}; };
} // namespace ZShell } // namespace ZShell
+43
View File
@@ -0,0 +1,43 @@
FROM archlinux:latest
RUN pacman -Syu --noconfirm
# Build toolchain
RUN pacman -S --noconfirm \
base-devel \
cmake \
ninja \
clang \
lld \
nodejs \
git
# Qt6
RUN pacman -S --noconfirm \
qt6-base \
qt6-declarative \
qt6-shadertools \
qt6-5compat \
qt6-canvaspainter
# Project-specific dependencies
RUN pacman -S --noconfirm \
libqalculate \
pipewire \
aubio \
cava \
lm_sensors \
glib2
# AUR: libcava (provides development headers for cava)
RUN pacman -S --needed --noconfirm sudo && \
useradd -m builder && \
echo "builder ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers && \
su builder -c " \
git clone https://aur.archlinux.org/libcava.git /tmp/libcava && \
cd /tmp/libcava && \
makepkg -si --noconfirm \
" && \
rm -rf /tmp/libcava && \
userdel -r builder && \
sed -i '/^builder/d' /etc/sudoers