Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 3s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 10s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 49s
Python / verify (pull_request) Successful in 1m40s
Rust / fmt (pull_request) Successful in 27s
Rust / clippy (pull_request) Successful in 46s

This commit is contained in:
2026-09-30 17:59:14 +02:00
298 changed files with 18329 additions and 6057 deletions
+16 -1
View File
@@ -1,19 +1,27 @@
qml_module(ZShell-config
URI ZShell.Config
INCLUDE_PREFIX config
DEPENDENCIES
ZShell.Settings
SOURCES
confignode.hpp confignode.cpp
configobject.hpp configobject.cpp
configlist.hpp configlist.cpp
config.hpp config.cpp
migration.hpp migration.cpp
anim.hpp anim.cpp
fontbuilder.hpp fontbuilder.cpp
font.hpp font.cpp
tokens.hpp
appearance.hpp
appearance.hpp appearance.cpp
background.hpp
bar.hpp
clipboard.hpp
colors.hpp
dashboard.hpp
display.hpp
dock.hpp
enums.hpp
general.hpp
launcher.hpp
llm.hpp
@@ -25,3 +33,10 @@ qml_module(ZShell-config
sidebar.hpp
utilities.hpp
)
target_link_libraries(ZShell-config PUBLIC
Qt::Gui
zshell-util
ZShell
ZShell-settings
)
+83 -16
View File
@@ -1,32 +1,82 @@
#include "anim.hpp"
#include "tokens.hpp"
#include <qpoint.h>
#include "appearance.hpp"
#include "tokens.hpp"
namespace ZShell::config {
QEasingCurve AnimEasing::buildCurve(const QList<qreal>& points) {
AnimTokens::AnimTokens(QObject* parent) : QObject(parent) {}
QEasingCurve AnimTokens::emphasized() const {
return m_emphasized;
}
QEasingCurve AnimTokens::emphasizedAccel() const {
return m_emphasizedAccel;
}
QEasingCurve AnimTokens::emphasizedDecel() const {
return m_emphasizedDecel;
}
QEasingCurve AnimTokens::standard() const {
return m_standard;
}
QEasingCurve AnimTokens::standardAccel() const {
return m_standardAccel;
}
QEasingCurve AnimTokens::standardDecel() const {
return m_standardDecel;
}
QEasingCurve AnimTokens::expressiveFastSpatial() const {
return m_expressiveFastSpatial;
}
QEasingCurve AnimTokens::expressiveDefaultSpatial() const {
return m_expressiveDefaultSpatial;
}
QEasingCurve AnimTokens::expressiveSlowSpatial() const {
return m_expressiveSlowSpatial;
}
QEasingCurve AnimTokens::expressiveFastEffects() const {
return m_expressiveFastEffects;
}
QEasingCurve AnimTokens::expressiveDefaultEffects() const {
return m_expressiveDefaultEffects;
}
QEasingCurve AnimTokens::expressiveSlowEffects() const {
return m_expressiveSlowEffects;
}
AnimDurations* AnimTokens::durations() const {
return m_durations;
}
QEasingCurve AnimTokens::buildCurve(const QList<qreal>& points) {
QEasingCurve curve(QEasingCurve::BezierSpline);
for (int i = 0; i + 5 < points.size(); i += 6) {
QPointF c1(points[i], points[i + 1]);
QPointF c2(points[i + 2], points[i + 3]);
QPointF end(points[i + 4], points[i + 5]);
const QPointF c1(points[i], points[i + 1]);
const QPointF c2(points[i + 2], points[i + 3]);
const QPointF end(points[i + 4], points[i + 5]);
curve.addCubicBezierSegment(c1, c2, end);
}
return curve;
}
void AnimEasing::bindCurves(AnimCurves* curves) {
m_curves = curves;
connect(
curves,
&AnimCurves::propertiesChanged,
this,
&AnimEasing::rebuildCurves);
rebuildCurves();
}
void AnimEasing::rebuildCurves() {
void AnimTokens::rebuildCurves() {
if (!m_curves) return;
m_emphasized = buildCurve(m_curves->emphasized());
m_emphasizedAccel = buildCurve(m_curves->emphasizedAccel());
m_emphasizedDecel = buildCurve(m_curves->emphasizedDecel());
@@ -41,7 +91,24 @@ void AnimEasing::rebuildCurves() {
m_expressiveDefaultEffects =
buildCurve(m_curves->expressiveDefaultEffects());
m_expressiveSlowEffects = buildCurve(m_curves->expressiveSlowEffects());
emit curvesChanged();
}
void AnimTokens::bindCurves(AnimCurves* curves) {
m_curves = curves;
connect(
curves, &AnimCurves::optionChanged, this, &AnimTokens::rebuildCurves);
rebuildCurves();
}
void AnimTokens::bindDurations(AnimDurations* durations) {
if (m_durations == durations) return;
m_durations = durations;
emit durationsChanged();
}
} // namespace ZShell::config
+38 -37
View File
@@ -9,8 +9,10 @@ namespace ZShell::config {
class AnimCurves;
class AnimDurations;
class AnimEasing : public QObject {
class AnimTokens : public QObject {
Q_OBJECT
Q_MOC_INCLUDE("tokens.hpp")
Q_MOC_INCLUDE("appearance.hpp")
QML_ANONYMOUS
Q_PROPERTY(QEasingCurve emphasized READ emphasized NOTIFY curvesChanged)
@@ -42,54 +44,53 @@ class AnimEasing : public QObject {
QEasingCurve expressiveSlowEffects READ expressiveSlowEffects NOTIFY
curvesChanged)
Q_PROPERTY(
ZShell::config::AnimDurations* durations READ durations NOTIFY
durationsChanged)
public:
explicit AnimEasing(QObject* parent = nullptr) : QObject(parent) {}
explicit AnimTokens(QObject* parent = nullptr);
void bindCurves(AnimCurves* curves);
void bindDurations(AnimDurations* durations);
[[nodiscard]] QEasingCurve emphasized() const { return m_emphasized; }
[[nodiscard]] QEasingCurve emphasizedAccel() const {
return m_emphasizedAccel;
}
[[nodiscard]] QEasingCurve emphasizedDecel() const {
return m_emphasizedDecel;
}
[[nodiscard]] QEasingCurve standard() const { return m_standard; }
[[nodiscard]] QEasingCurve standardAccel() const { return m_standardAccel; }
[[nodiscard]] QEasingCurve standardDecel() const { return m_standardDecel; }
[[nodiscard]] QEasingCurve expressiveFastSpatial() const {
return m_expressiveFastSpatial;
}
[[nodiscard]] QEasingCurve expressiveDefaultSpatial() const {
return m_expressiveDefaultSpatial;
}
[[nodiscard]] QEasingCurve expressiveSlowSpatial() const {
return m_expressiveSlowSpatial;
}
[[nodiscard]] QEasingCurve expressiveFastEffects() const {
return m_expressiveFastEffects;
}
[[nodiscard]] QEasingCurve expressiveDefaultEffects() const {
return m_expressiveDefaultEffects;
}
[[nodiscard]] QEasingCurve expressiveSlowEffects() const {
return m_expressiveSlowEffects;
}
[[nodiscard]] QEasingCurve emphasized() const;
[[nodiscard]] QEasingCurve emphasizedAccel() const;
[[nodiscard]] QEasingCurve emphasizedDecel() const;
[[nodiscard]] QEasingCurve standard() const;
[[nodiscard]] QEasingCurve standardAccel() const;
[[nodiscard]] QEasingCurve standardDecel() const;
[[nodiscard]] QEasingCurve expressiveFastSpatial() const;
[[nodiscard]] QEasingCurve expressiveDefaultSpatial() const;
[[nodiscard]] QEasingCurve expressiveSlowSpatial() const;
[[nodiscard]] QEasingCurve expressiveFastEffects() const;
[[nodiscard]] QEasingCurve expressiveDefaultEffects() const;
[[nodiscard]] QEasingCurve expressiveSlowEffects() const;
[[nodiscard]] AnimDurations* durations() const;
signals:
void curvesChanged();
void durationsChanged();
private:
static QEasingCurve buildCurve(const QList<qreal>& points);
void rebuildCurves();
static QEasingCurve buildCurve(const QList<qreal>& points);
AnimCurves* m_curves = nullptr;
QEasingCurve m_emphasized, m_emphasizedAccel, m_emphasizedDecel;
QEasingCurve m_standard, m_standardAccel, m_standardDecel;
QEasingCurve m_expressiveFastSpatial, m_expressiveDefaultSpatial,
m_expressiveSlowSpatial;
QEasingCurve m_expressiveFastEffects, m_expressiveDefaultEffects,
m_expressiveSlowEffects;
AnimDurations* m_durations = nullptr;
QEasingCurve m_emphasized;
QEasingCurve m_emphasizedAccel;
QEasingCurve m_emphasizedDecel;
QEasingCurve m_standard;
QEasingCurve m_standardAccel;
QEasingCurve m_standardDecel;
QEasingCurve m_expressiveFastSpatial;
QEasingCurve m_expressiveDefaultSpatial;
QEasingCurve m_expressiveSlowSpatial;
QEasingCurve m_expressiveFastEffects;
QEasingCurve m_expressiveDefaultEffects;
QEasingCurve m_expressiveSlowEffects;
};
} // namespace ZShell::config
+219
View File
@@ -0,0 +1,219 @@
#include "appearance.hpp"
#include "tokens.hpp"
namespace ZShell::config {
namespace {
template <typename Source, typename Target>
void connectTokenSignals(Source* source, Target* target) {
const auto* meta = source->metaObject();
for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) {
auto prop = meta->property(i);
if (prop.hasNotifySignal())
QObject::connect(
source,
prop.notifySignal(),
target,
target->metaObject()->method(
target->metaObject()->indexOfSignal("valuesChanged()")));
}
QObject::connect(
target, &Target::scaleChanged, target, &Target::valuesChanged);
}
} // namespace
void AppearanceRounding::bindTokens(RoundingTokens* tokens) {
m_tokens = tokens;
connectTokenSignals(tokens, this);
}
int AppearanceRounding::extraSmall() const {
return m_tokens ? static_cast<int>(m_tokens->extraSmall() * m_scale) : 0;
}
int AppearanceRounding::small() const {
return m_tokens ? static_cast<int>(m_tokens->small() * m_scale) : 0;
}
int AppearanceRounding::medium() const {
return m_tokens ? static_cast<int>(m_tokens->medium() * m_scale) : 0;
}
int AppearanceRounding::large() const {
return m_tokens ? static_cast<int>(m_tokens->large() * m_scale) : 0;
}
int AppearanceRounding::largeIncreased() const {
return m_tokens ? static_cast<int>(m_tokens->largeIncreased() * m_scale)
: 0;
}
int AppearanceRounding::extraLarge() const {
return m_tokens ? static_cast<int>(m_tokens->extraLarge() * m_scale) : 0;
}
int AppearanceRounding::extraLargeIncreased() const {
return m_tokens
? static_cast<int>(m_tokens->extraLargeIncreased() * m_scale)
: 0;
}
int AppearanceRounding::extraExtraLarge() const {
return m_tokens ? static_cast<int>(m_tokens->extraExtraLarge() * m_scale)
: 0;
}
int AppearanceRounding::full() const {
return m_tokens ? m_tokens->full() : 0;
}
void AppearanceSpacing::bindTokens(SpacingTokens* tokens) {
m_tokens = tokens;
connectTokenSignals(tokens, this);
}
int AppearanceSpacing::extraSmall() const {
return m_tokens ? static_cast<int>(m_tokens->extraSmall() * m_scale) : 0;
}
int AppearanceSpacing::small() const {
return m_tokens ? static_cast<int>(m_tokens->small() * m_scale) : 0;
}
int AppearanceSpacing::medium() const {
return m_tokens ? static_cast<int>(m_tokens->medium() * m_scale) : 0;
}
int AppearanceSpacing::large() const {
return m_tokens ? static_cast<int>(m_tokens->large() * m_scale) : 0;
}
int AppearanceSpacing::largeIncreased() const {
return m_tokens ? static_cast<int>(m_tokens->largeIncreased() * m_scale)
: 0;
}
int AppearanceSpacing::extraLarge() const {
return m_tokens ? static_cast<int>(m_tokens->extraLarge() * m_scale) : 0;
}
int AppearanceSpacing::extraLargeIncreased() const {
return m_tokens
? static_cast<int>(m_tokens->extraLargeIncreased() * m_scale)
: 0;
}
int AppearanceSpacing::extraExtraLarge() const {
return m_tokens ? static_cast<int>(m_tokens->extraExtraLarge() * m_scale)
: 0;
}
void AppearancePadding::bindTokens(PaddingTokens* tokens) {
m_tokens = tokens;
connectTokenSignals(tokens, this);
}
int AppearancePadding::extraSmall() const {
return m_tokens ? static_cast<int>(m_tokens->extraSmall() * m_scale) : 0;
}
int AppearancePadding::smaller() const {
return m_tokens ? static_cast<int>(m_tokens->smaller() * m_scale) : 0;
}
int AppearancePadding::small() const {
return m_tokens ? static_cast<int>(m_tokens->small() * m_scale) : 0;
}
int AppearancePadding::medium() const {
return m_tokens ? static_cast<int>(m_tokens->medium() * m_scale) : 0;
}
int AppearancePadding::large() const {
return m_tokens ? static_cast<int>(m_tokens->large() * m_scale) : 0;
}
int AppearancePadding::largeIncreased() const {
return m_tokens ? static_cast<int>(m_tokens->largeIncreased() * m_scale)
: 0;
}
int AppearancePadding::extraLarge() const {
return m_tokens ? static_cast<int>(m_tokens->extraLarge() * m_scale) : 0;
}
int AppearancePadding::extraLargeIncreased() const {
return m_tokens
? static_cast<int>(m_tokens->extraLargeIncreased() * m_scale)
: 0;
}
int AppearancePadding::extraExtraLarge() const {
return m_tokens ? static_cast<int>(m_tokens->extraExtraLarge() * m_scale)
: 0;
}
void AnimDurations::bindTokens(AnimDurationTokens* tokens) {
m_tokens = tokens;
connectTokenSignals(tokens, this);
}
int AnimDurations::small() const {
return m_tokens ? static_cast<int>(m_tokens->small() * m_scale) : 0;
}
int AnimDurations::normal() const {
return m_tokens ? static_cast<int>(m_tokens->normal() * m_scale) : 0;
}
int AnimDurations::large() const {
return m_tokens ? static_cast<int>(m_tokens->large() * m_scale) : 0;
}
int AnimDurations::extraLarge() const {
return m_tokens ? static_cast<int>(m_tokens->extraLarge() * m_scale) : 0;
}
int AnimDurations::expressiveFastSpatial() const {
return m_tokens
? static_cast<int>(m_tokens->expressiveFastSpatial() * m_scale)
: 0;
}
int AnimDurations::expressiveDefaultSpatial() const {
return m_tokens ? static_cast<int>(
m_tokens->expressiveDefaultSpatial() * m_scale)
: 0;
}
int AnimDurations::expressiveSlowSpatial() const {
return m_tokens
? static_cast<int>(m_tokens->expressiveSlowSpatial() * m_scale)
: 0;
}
int AnimDurations::expressiveFastEffects() const {
return m_tokens
? static_cast<int>(m_tokens->expressiveFastEffects() * m_scale)
: 0;
}
int AnimDurations::expressiveDefaultEffects() const {
return m_tokens ? static_cast<int>(
m_tokens->expressiveDefaultEffects() * m_scale)
: 0;
}
int AnimDurations::expressiveSlowEffects() const {
return m_tokens
? static_cast<int>(m_tokens->expressiveSlowEffects() * m_scale)
: 0;
}
} // namespace ZShell::config
+312 -48
View File
@@ -1,83 +1,347 @@
#pragma once
#include "configobject.hpp"
#include <qqmlregistration.h>
#include <qfont.h>
#include <qstring.h>
#include <qvariantmap.h>
#include "settings/objectnode.hpp"
#include "common.hpp"
namespace ZShell::config {
class Anim : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
using Qt::StringLiterals::operator""_s;
CFG_PROPERTY(int, mediaGifSpeedAdjustment, 300)
CFG_PROPERTY(qreal, sessionGifSpeed, 0.7)
class RoundingTokens;
class SpacingTokens;
class PaddingTokens;
class AnimDurationTokens;
class AppearanceRounding : public settings::ObjectNode {
CONFIG_NODE(AppearanceRounding, settings::ObjectNode)
CONFIG_PROPERTY(qreal, scale, 1)
Q_PROPERTY(int extraSmall READ extraSmall NOTIFY valuesChanged)
Q_PROPERTY(int small READ small NOTIFY valuesChanged)
Q_PROPERTY(int medium READ medium NOTIFY valuesChanged)
Q_PROPERTY(int large READ large NOTIFY valuesChanged)
Q_PROPERTY(int largeIncreased READ largeIncreased NOTIFY valuesChanged)
Q_PROPERTY(int extraLarge READ extraLarge NOTIFY valuesChanged)
Q_PROPERTY(
int extraLargeIncreased READ extraLargeIncreased NOTIFY valuesChanged)
Q_PROPERTY(int extraExtraLarge READ extraExtraLarge NOTIFY valuesChanged)
Q_PROPERTY(int full READ full NOTIFY valuesChanged)
public:
explicit Anim(QObject* parent = nullptr) : ConfigObject(parent) {}
void bindTokens(RoundingTokens* tokens);
[[nodiscard]] int extraSmall() const;
[[nodiscard]] int small() const;
[[nodiscard]] int medium() const;
[[nodiscard]] int large() const;
[[nodiscard]] int largeIncreased() const;
[[nodiscard]] int extraLarge() const;
[[nodiscard]] int extraLargeIncreased() const;
[[nodiscard]] int extraExtraLarge() const;
[[nodiscard]] int full() const;
signals:
void valuesChanged();
private:
RoundingTokens* m_tokens = nullptr;
};
class Deform : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class AppearanceSpacing : public settings::ObjectNode {
CONFIG_NODE(AppearanceSpacing, settings::ObjectNode)
CFG_PROPERTY(qreal, scale, 1)
CONFIG_PROPERTY(qreal, scale, 1)
Q_PROPERTY(int extraSmall READ extraSmall NOTIFY valuesChanged)
Q_PROPERTY(int small READ small NOTIFY valuesChanged)
Q_PROPERTY(int medium READ medium NOTIFY valuesChanged)
Q_PROPERTY(int large READ large NOTIFY valuesChanged)
Q_PROPERTY(int largeIncreased READ largeIncreased NOTIFY valuesChanged)
Q_PROPERTY(int extraLarge READ extraLarge NOTIFY valuesChanged)
Q_PROPERTY(
int extraLargeIncreased READ extraLargeIncreased NOTIFY valuesChanged)
Q_PROPERTY(int extraExtraLarge READ extraExtraLarge NOTIFY valuesChanged)
public:
explicit Deform(QObject* parent = nullptr) : ConfigObject(parent) {}
void bindTokens(SpacingTokens* tokens);
[[nodiscard]] int extraSmall() const;
[[nodiscard]] int small() const;
[[nodiscard]] int medium() const;
[[nodiscard]] int large() const;
[[nodiscard]] int largeIncreased() const;
[[nodiscard]] int extraLarge() const;
[[nodiscard]] int extraLargeIncreased() const;
[[nodiscard]] int extraExtraLarge() const;
signals:
void valuesChanged();
private:
SpacingTokens* m_tokens = nullptr;
};
class FontFamily : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class AppearancePadding : public settings::ObjectNode {
CONFIG_NODE(AppearancePadding, settings::ObjectNode)
CFG_PROPERTY(QString, clock, QStringLiteral("Rubik"))
CFG_PROPERTY(QString, material, QStringLiteral("Material Symbols Rounded"))
CFG_PROPERTY(QString, mono, QStringLiteral("CaskaydiaCove NF"))
CFG_PROPERTY(QString, sans, QStringLiteral("Segoe UI Variable Text"))
CONFIG_PROPERTY(qreal, scale, 1)
Q_PROPERTY(int extraSmall READ extraSmall NOTIFY valuesChanged)
Q_PROPERTY(int smaller READ smaller NOTIFY valuesChanged)
Q_PROPERTY(int small READ small NOTIFY valuesChanged)
Q_PROPERTY(int medium READ medium NOTIFY valuesChanged)
Q_PROPERTY(int large READ large NOTIFY valuesChanged)
Q_PROPERTY(int largeIncreased READ largeIncreased NOTIFY valuesChanged)
Q_PROPERTY(int extraLarge READ extraLarge NOTIFY valuesChanged)
Q_PROPERTY(
int extraLargeIncreased READ extraLargeIncreased NOTIFY valuesChanged)
Q_PROPERTY(int extraExtraLarge READ extraExtraLarge NOTIFY valuesChanged)
public:
explicit FontFamily(QObject* parent = nullptr) : ConfigObject(parent) {}
void bindTokens(PaddingTokens* tokens);
[[nodiscard]] int extraSmall() const;
[[nodiscard]] int smaller() const;
[[nodiscard]] int small() const;
[[nodiscard]] int medium() const;
[[nodiscard]] int large() const;
[[nodiscard]] int largeIncreased() const;
[[nodiscard]] int extraLarge() const;
[[nodiscard]] int extraLargeIncreased() const;
[[nodiscard]] int extraExtraLarge() const;
signals:
void valuesChanged();
private:
PaddingTokens* m_tokens = nullptr;
};
class Font : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
namespace detail {
struct FontConfig {
QString family = {};
int size;
int weight = QFont::Normal;
bool italic = false;
QVariantMap vaxes = {{u"ROND"_s, 25}};
};
} // namespace detail
class FontSizeNode : public settings::ObjectNode {
CONFIG_NODE(FontSizeNode, settings::ObjectNode)
};
class FontStyleNode : public settings::ObjectNode {
CONFIG_NODE(FontStyleNode, settings::ObjectNode)
public:
virtual FontSizeNode* largeNode() const = 0;
virtual FontSizeNode* mediumNode() const = 0;
virtual FontSizeNode* smallNode() const = 0;
virtual FontSizeNode* extraLargeNode() const { return nullptr; }
};
#define ARG(...) __VA_ARGS__
#define FONT(...) ZShell::config::detail::FontConfig __VA_ARGS__
#define FONT_CONFIG(Style, Size, props) \
class FontConfig##Style##Size : public FontSizeNode { \
CONFIG_NODE(FontConfig##Style##Size, FontSizeNode) \
\
CONFIG_PROPERTY( \
QString, \
family, \
ARG(props).family) /* Empty inherits the style family */ \
CONFIG_PROPERTY(int, size, ARG(props).size) \
CONFIG_PROPERTY(int, weight, ARG(props).weight) \
CONFIG_PROPERTY(bool, italic, ARG(props).italic) \
CONFIG_PROPERTY(QVariantMap, vaxes, ARG(props).vaxes) \
};
#define FONT_STYLE(Style, family_, large_, medium_, small_) \
FONT_CONFIG(Style, Large, ARG(large_)) \
FONT_CONFIG(Style, Medium, ARG(medium_)) \
FONT_CONFIG(Style, Small, ARG(small_)) \
\
class FontStyle##Style : public FontStyleNode { \
CONFIG_NODE(FontStyle##Style, FontStyleNode) \
\
CONFIG_PROPERTY(QString, family, family_) \
CONFIG_SUBOBJECT(FontConfig##Style##Large, large) \
CONFIG_SUBOBJECT(FontConfig##Style##Medium, medium) \
CONFIG_SUBOBJECT(FontConfig##Style##Small, small) \
\
public: \
FontSizeNode* largeNode() const override { return m_large; } \
FontSizeNode* mediumNode() const override { return m_medium; } \
FontSizeNode* smallNode() const override { return m_small; } \
};
// clang-format off
FONT_STYLE(Headline, u"GoogleSansFlex"_s,
FONT({ .size = 32, .weight = QFont::Medium }),
FONT({ .size = 28, .weight = QFont::Medium }),
FONT({ .size = 24, .weight = QFont::Medium })
)
FONT_STYLE(Title, u"GoogleSansFlex"_s,
FONT({ .size = 22, .weight = QFont::Medium }),
FONT({ .size = 16, .weight = QFont::Medium }),
FONT({ .size = 14, .weight = QFont::Medium })
)
FONT_STYLE(Body, u"GoogleSansFlex"_s,
FONT({ .size = 16 }),
FONT({ .size = 14 }),
FONT({ .size = 12 })
)
FONT_STYLE(Label, u"GoogleSansFlex"_s,
FONT({ .size = 14, .weight = QFont::Medium }),
FONT({ .size = 12, .weight = QFont::Medium }),
FONT({ .size = 11 })
)
FONT_STYLE(Mono, u"CaskaydiaCove NF"_s,
FONT({ .size = 16, .vaxes = {} }),
FONT({ .size = 14, .vaxes = {} }),
FONT({ .size = 12, .vaxes = {} })
)
// clang-format on
FONT_CONFIG(Icon, ExtraLarge, FONT({.size = static_cast<int>(48 / 1.33)}))
FONT_CONFIG(Icon, Large, FONT({.size = static_cast<int>(32 / 1.33)}))
FONT_CONFIG(Icon, Medium, FONT({.size = static_cast<int>(24 / 1.33)}))
FONT_CONFIG(Icon, Small, FONT({.size = static_cast<int>(20 / 1.33)}))
class FontStyleIcon : public FontStyleNode {
CONFIG_NODE(FontStyleIcon, FontStyleNode)
CONFIG_PROPERTY(QString, family, u"Material Symbols Rounded"_s)
CONFIG_SUBOBJECT(FontConfigIconExtraLarge, extraLarge)
CONFIG_SUBOBJECT(FontConfigIconLarge, large)
CONFIG_SUBOBJECT(FontConfigIconMedium, medium)
CONFIG_SUBOBJECT(FontConfigIconSmall, small)
public:
FontSizeNode* extraLargeNode() const override { return m_extraLarge; }
FontSizeNode* largeNode() const override { return m_large; }
FontSizeNode* mediumNode() const override { return m_medium; }
FontSizeNode* smallNode() const override { return m_small; }
};
#undef ARG
#undef FONT
#undef FONT_CONFIG
#undef FONT_STYLE
class FontFamily : public settings::ObjectNode {
CONFIG_NODE(FontFamily, settings::ObjectNode)
CONFIG_PROPERTY(QString, clock, QStringLiteral("Rubik"))
CONFIG_PROPERTY(
QString, material, QStringLiteral("Material Symbols Rounded"))
CONFIG_PROPERTY(QString, mono, QStringLiteral("CaskaydiaCove NF"))
CONFIG_PROPERTY(QString, sans, QStringLiteral("Segoe UI Variable Text"))
};
class AppearanceFont : public settings::ObjectNode {
CONFIG_NODE(AppearanceFont, settings::ObjectNode)
CONFIG_PROPERTY(qreal, scale, 1)
CONFIG_SUBOBJECT(FontFamily, family)
public:
explicit Font(QObject* parent = nullptr)
: ConfigObject(parent), m_family(new FontFamily(this)) {}
CONFIG_SUBOBJECT(FontStyleHeadline, headline)
CONFIG_SUBOBJECT(FontStyleTitle, title)
CONFIG_SUBOBJECT(FontStyleBody, body)
CONFIG_SUBOBJECT(FontStyleLabel, label)
CONFIG_SUBOBJECT(FontStyleMono, mono)
CONFIG_SUBOBJECT(FontStyleIcon, icon)
CONFIG_PROPERTY(QString, clock, QStringLiteral("Rubik"))
CONFIG_PROPERTY(QString, workspaces, QStringLiteral("Rubik"))
};
class Transparency : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class AnimDurations : public settings::ObjectNode {
CONFIG_NODE(AnimDurations, settings::ObjectNode)
CFG_PROPERTY(qreal, base, 0.85)
CFG_PROPERTY(bool, enabled, false)
CFG_PROPERTY(qreal, layers, 0.4)
CONFIG_GLOBAL_PROPERTY(qreal, scale, 1)
Q_PROPERTY(int small READ small NOTIFY valuesChanged)
Q_PROPERTY(int normal READ normal NOTIFY valuesChanged)
Q_PROPERTY(int large READ large NOTIFY valuesChanged)
Q_PROPERTY(int extraLarge READ extraLarge NOTIFY valuesChanged)
Q_PROPERTY(
int expressiveFastSpatial READ expressiveFastSpatial NOTIFY
valuesChanged)
Q_PROPERTY(
int expressiveDefaultSpatial READ expressiveDefaultSpatial NOTIFY
valuesChanged)
Q_PROPERTY(
int expressiveSlowSpatial READ expressiveSlowSpatial NOTIFY
valuesChanged)
Q_PROPERTY(
int expressiveFastEffects READ expressiveFastEffects NOTIFY
valuesChanged)
Q_PROPERTY(
int expressiveDefaultEffects READ expressiveDefaultEffects NOTIFY
valuesChanged)
Q_PROPERTY(
int expressiveSlowEffects READ expressiveSlowEffects NOTIFY
valuesChanged)
public:
explicit Transparency(QObject* parent = nullptr) : ConfigObject(parent) {}
void bindTokens(AnimDurationTokens* tokens);
[[nodiscard]] int small() const;
[[nodiscard]] int normal() const;
[[nodiscard]] int large() const;
[[nodiscard]] int extraLarge() const;
[[nodiscard]] int expressiveFastSpatial() const;
[[nodiscard]] int expressiveDefaultSpatial() const;
[[nodiscard]] int expressiveSlowSpatial() const;
[[nodiscard]] int expressiveFastEffects() const;
[[nodiscard]] int expressiveDefaultEffects() const;
[[nodiscard]] int expressiveSlowEffects() const;
signals:
void valuesChanged();
private:
AnimDurationTokens* m_tokens = nullptr;
};
class Appearance : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Anim : public settings::ObjectNode {
CONFIG_NODE(Anim, settings::ObjectNode)
CONFIG_PROPERTY(int, mediaGifSpeedAdjustment, 300)
CONFIG_PROPERTY(qreal, sessionGifSpeed, 0.7)
CONFIG_SUBOBJECT(AnimDurations, durations)
};
class Deform : public settings::ObjectNode {
CONFIG_NODE(Deform, settings::ObjectNode)
CONFIG_PROPERTY(qreal, scale, 1)
};
class AppearanceTransparency : public settings::ObjectNode {
CONFIG_NODE(AppearanceTransparency, settings::ObjectNode)
CONFIG_GLOBAL_PROPERTY(bool, enabled, false)
CONFIG_GLOBAL_PROPERTY(qreal, base, 0.85)
CONFIG_GLOBAL_PROPERTY(qreal, layers, 0.4)
};
class Appearance : public settings::ObjectNode {
CONFIG_NODE(Appearance, settings::ObjectNode)
CONFIG_SUBOBJECT(Anim, anim)
CONFIG_SUBOBJECT(Deform, deform)
CONFIG_SUBOBJECT(Font, font)
CONFIG_SUBOBJECT(Transparency, transparency)
public:
explicit Appearance(QObject* parent = nullptr)
: ConfigObject(parent)
, m_anim(new Anim(this))
, m_deform(new Deform(this))
, m_font(new Font(this))
, m_transparency(new Transparency(this)) {}
CONFIG_SUBOBJECT(AppearanceFont, font)
CONFIG_SUBOBJECT(AppearanceRounding, rounding)
CONFIG_SUBOBJECT(AppearanceSpacing, spacing)
CONFIG_SUBOBJECT(AppearancePadding, padding)
CONFIG_SUBOBJECT(AppearanceTransparency, transparency)
};
} // namespace ZShell::config
+5 -9
View File
@@ -1,18 +1,14 @@
#pragma once
#include "configobject.hpp"
#include "common.hpp"
#include <qqmlintegration.h>
namespace ZShell::config {
class Background : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Background : public settings::ObjectNode {
CONFIG_NODE(Background, settings::ObjectNode)
CFG_PROPERTY(bool, enabled, true)
CFG_PROPERTY(int, wallFadeDuration, 300)
public:
explicit Background(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(bool, enabled, true)
CONFIG_PROPERTY(int, wallFadeDuration, 300)
};
} // namespace ZShell::config
+28 -47
View File
@@ -1,7 +1,6 @@
#pragma once
#include "configlist.hpp"
#include "configobject.hpp"
#include "common.hpp"
#include <qcontainerfwd.h>
#include <qhashfunctions.h>
#include <qqmlregistration.h>
@@ -9,66 +8,53 @@
namespace ZShell::config {
class BarEntry : public ListEntry {
Q_OBJECT
QML_ANONYMOUS
public:
explicit BarEntry(QObject* parent = nullptr) : ListEntry(parent) {}
CONFIG_NODE(BarEntry, ListEntry)
};
class Popouts : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Popouts : public settings::ObjectNode {
CONFIG_NODE(Popouts, settings::ObjectNode)
CFG_PROPERTY(bool, statusIcons, true)
CFG_PROPERTY(bool, tray, true)
public:
explicit Popouts(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(bool, statusIcons, true)
CONFIG_PROPERTY(bool, tray, true)
};
class Tray : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Tray : public settings::ObjectNode {
CONFIG_NODE(Tray, settings::ObjectNode)
CFG_PROPERTY(bool, recolorIcons, false)
CFG_PROPERTY(bool, showOnHover, true)
CFG_PROPERTY(int, trayIconSize, 24)
CONFIG_PROPERTY(bool, recolorIcons, false)
CONFIG_PROPERTY(bool, showOnHover, true)
CONFIG_PROPERTY(int, trayIconSize, 24)
CONFIG_LIST(
EntryList,
statusIcons,
{
DEFAULT_ARG({
LIST_ENTRY(audio, true),
LIST_ENTRY(microphone, true),
LIST_ENTRY(bluetooth, false),
LIST_ENTRY(network, false),
LIST_ENTRY(wifi, false),
LIST_ENTRY(power, true),
})
public:
explicit Tray(QObject* parent = nullptr) : ConfigObject(parent) {}
}))
};
class Bar : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Bar : public settings::ObjectNode {
CONFIG_NODE(Bar, settings::ObjectNode)
CFG_PROPERTY(bool, autoHide, false)
CFG_PROPERTY(int, border, 4)
CFG_PROPERTY(bool, fullscreenReveal, true)
CFG_PROPERTY(int, height, 24)
CFG_PROPERTY(bool, hideWhenNotif, true)
CONFIG_PROPERTY(bool, autoHide, false)
CONFIG_PROPERTY(int, border, 4)
CONFIG_PROPERTY(bool, fullscreenReveal, true)
CONFIG_PROPERTY(int, height, 24)
CONFIG_PROPERTY(bool, hideWhenNotif, true)
CONFIG_SUBOBJECT(Popouts, popouts)
CFG_PROPERTY(int, revealDelay, 100)
CFG_PROPERTY(int, rounding, 14)
CFG_PROPERTY(int, smoothing, 32)
CFG_PROPERTY(QString, position, QStringLiteral("top"))
CONFIG_PROPERTY(int, revealDelay, 100)
CONFIG_PROPERTY(int, rounding, 14)
CONFIG_PROPERTY(int, smoothing, 32)
CONFIG_PROPERTY(QString, position, QStringLiteral("top"))
CONFIG_SUBOBJECT(Tray, tray)
CONFIG_LIST(
EntryList,
entries,
{
DEFAULT_ARG({
LIST_ENTRY(workspaces, true),
LIST_ENTRY(audio, true),
LIST_ENTRY(media, true),
@@ -78,20 +64,15 @@ class Bar : public ConfigObject {
LIST_ENTRY(spacer, true),
LIST_ENTRY(activeWindow, true),
LIST_ENTRY(spacer, true),
LIST_ENTRY(hyprsunset, true),
LIST_ENTRY(nightlight, true),
LIST_ENTRY(tray, true),
LIST_ENTRY(statusIcons, true),
LIST_ENTRY(network, false),
LIST_ENTRY(upower, true),
LIST_ENTRY(clock, true),
LIST_ENTRY(notifBell, true),
})
public:
explicit Bar(QObject* parent = nullptr)
: ConfigObject(parent)
, m_popouts(new Popouts(this))
, m_tray(new Tray(this)) {}
}),
.identityKey = QStringLiteral("id"))
};
} // namespace ZShell::config
+11 -20
View File
@@ -1,33 +1,24 @@
#pragma once
#include "configobject.hpp"
#include "common.hpp"
#include <qqmlintegration.h>
namespace ZShell::config {
class ClipboardSizes : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class ClipboardSizes : public settings::ObjectNode {
CONFIG_NODE(ClipboardSizes, settings::ObjectNode)
CFG_PROPERTY(int, itemHeight, 60)
CFG_PROPERTY(int, minPreviewWidth, 200)
CFG_PROPERTY(int, previewWidth, 800)
CFG_PROPERTY(int, width, 500)
public:
explicit ClipboardSizes(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(int, itemHeight, 60)
CONFIG_PROPERTY(int, minPreviewWidth, 200)
CONFIG_PROPERTY(int, previewWidth, 800)
CONFIG_PROPERTY(int, width, 500)
};
class Clipboard : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Clipboard : public settings::ObjectNode {
CONFIG_NODE(Clipboard, settings::ObjectNode)
CFG_PROPERTY(bool, enabled, true)
CFG_PROPERTY(int, maxEntriesShown, 10)
CONFIG_PROPERTY(bool, enabled, true)
CONFIG_PROPERTY(int, maxEntriesShown, 10)
CONFIG_SUBOBJECT(ClipboardSizes, sizes)
public:
explicit Clipboard(QObject* parent = nullptr)
: ConfigObject(parent), m_sizes(new ClipboardSizes(this)) {}
};
} // namespace ZShell::config
+16 -18
View File
@@ -1,31 +1,29 @@
#pragma once
#include "configobject.hpp"
#include "common.hpp"
#include <qqmlintegration.h>
namespace ZShell::config {
class Presets : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Presets : public settings::ObjectNode {
CONFIG_NODE(Presets, settings::ObjectNode)
CFG_PROPERTY(QString, accent, QString())
CFG_PROPERTY(QString, name, QStringLiteral("Catppuccin"))
CFG_PROPERTY(QString, variant, QStringLiteral("latte"))
public:
explicit Presets(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(QString, accent, QString())
CONFIG_PROPERTY(QString, name, QStringLiteral("Catppuccin"))
CONFIG_PROPERTY(QString, variant, QStringLiteral("latte"))
};
class Colors : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Colors : public settings::ObjectNode {
CONFIG_NODE(Colors, settings::ObjectNode)
CONFIG_SUBOBJECT(Presets, presets)
CFG_PROPERTY(QString, schemeType, QStringLiteral("fidelity"))
public:
explicit Colors(QObject* parent = nullptr)
: ConfigObject(parent), m_presets(new Presets(this)) {}
CONFIG_PROPERTY(QString, schemeType, QStringLiteral("fidelity"))
CONFIG_PROPERTY(bool, scheduleDark, false)
CONFIG_PROPERTY(int, scheduleDarkEnd, 600)
CONFIG_PROPERTY(int, scheduleDarkStart, 1140)
CONFIG_PROPERTY(bool, schemeGen, true)
CONFIG_PROPERTY(bool, neovimColors, false)
CONFIG_PROPERTY(QString, mode, QStringLiteral("dark"))
CONFIG_PROPERTY(bool, smart, false)
};
} // namespace ZShell::config
+22
View File
@@ -0,0 +1,22 @@
#include "common.hpp"
#include <QDir>
#include <QStandardPaths>
namespace ZShell::config {
Q_LOGGING_CATEGORY(lcConfig, "zshell.config", QtInfoMsg)
QString configDir() {
const QString dir =
QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) +
QStringLiteral("/zshell");
QDir().mkpath(dir);
return dir;
}
QString monitorConfigDir() {
return configDir() + QStringLiteral("/monitors");
}
} // namespace ZShell::config
+54
View File
@@ -0,0 +1,54 @@
#pragma once
#include <qloggingcategory.h>
#include <qstring.h>
#include "settings/macros.hpp"
#include "settings/objectnode.hpp"
#define CONFIG_NODE_NO_CTOR SETTINGS_NODE_NO_CTOR
#define CONFIG_NODE SETTINGS_NODE
#define CONFIG_PROPERTY SETTINGS_PROPERTY
#define CONFIG_GLOBAL_PROPERTY SETTINGS_GLOBAL_PROPERTY
#define CONFIG_ENUM_PROPERTY(Type, name, defaultVal) \
CONFIG_PROPERTY(ZShell::config::Type::Enum, name, defaultVal)
#define CONFIG_GLOBAL_ENUM_PROPERTY(Type, name, defaultVal) \
CONFIG_GLOBAL_PROPERTY(ZShell::config::Type::Enum, name, defaultVal)
#define CONFIG_SUBOBJECT(Type, name) \
SETTINGS_SUBOBJECT(ZShell::config::Type, name)
#define CONFIG_GLOBAL_SUBOBJECT(Type, name) \
SETTINGS_GLOBAL_SUBOBJECT(ZShell::config::Type, name)
#define CONFIG_LIST_TYPE SETTINGS_LIST_TYPE
#define CONFIG_LIST(Type, name, defaultVal, ...) \
SETTINGS_LIST( \
ZShell::config::Type, name, DEFAULT_ARG(defaultVal), __VA_ARGS__)
#define CONFIG_GLOBAL_LIST(Type, name, defaultVal, ...) \
SETTINGS_GLOBAL_LIST( \
ZShell::config::Type, name, DEFAULT_ARG(defaultVal), __VA_ARGS__)
namespace ZShell::config {
Q_DECLARE_LOGGING_CATEGORY(lcConfig)
QString configDir();
QString monitorConfigDir();
class ListEntry : public settings::ObjectNode {
CONFIG_NODE(ListEntry, settings::ObjectNode)
CONFIG_PROPERTY(QString, id, {})
CONFIG_PROPERTY(bool, enabled, true)
};
CONFIG_LIST_TYPE(ListEntry, EntryList)
} // namespace ZShell::config
// Shorthand for declaring an ID'd entry (bar entries/status icons, quick toggles, etc)
#define LIST_ENTRY(id, enabled) \
settings::vmap({ \
{QStringLiteral("id"), QStringLiteral(#id)}, \
{QStringLiteral("enabled"), enabled}, \
})
-288
View File
@@ -1,288 +0,0 @@
#include "config.hpp"
#include "appearance.hpp"
#include "background.hpp"
#include "bar.hpp"
#include "clipboard.hpp"
#include "colors.hpp"
#include "dashboard.hpp"
#include "dock.hpp"
#include "general.hpp"
#include "launcher.hpp"
#include "llm.hpp"
#include "lock.hpp"
#include "notifs.hpp"
#include "osd.hpp"
#include "screenshot.hpp"
#include "services.hpp"
#include "sidebar.hpp"
#include "utilities.hpp"
#include <QFile>
#include <QSaveFile>
#include <QJsonDocument>
#include <QStandardPaths>
#include <QDir>
#include <QDebug>
#include <QFutureWatcher>
#include <QFileInfo>
#include <QFileSystemWatcher>
#include <qcontainerfwd.h>
#include <qfileinfo.h>
#include <qfilesystemwatcher.h>
#include <qthreadpool.h>
namespace ZShell::config {
Config* Config::s_instance = nullptr;
Config::Config(QObject* parent)
: ConfigObject(parent)
, m_appearance(new Appearance(this))
, m_background(new Background(this))
, m_bar(new Bar(this))
, m_clipboard(new Clipboard(this))
, m_colors(new Colors(this))
, m_dashboard(new Dashboard(this))
, m_dock(new Dock(this))
, m_general(new General(this))
, m_launcher(new Launcher(this))
, m_llm(new Llm(this))
, m_lock(new Lock(this))
, m_notifs(new Notifs(this))
, m_osd(new Osd(this))
, m_screenshot(new Screenshot(this))
, m_services(new Services(this))
, m_sidebar(new Sidebar(this))
, m_utilities(new Utilities(this)) {
s_instance = this;
connect(this, &ConfigObject::propertiesChanged, this, &Config::scheduleSave);
m_saveTimer.setSingleShot(true);
m_saveTimer.setInterval(300);
connect(&m_saveTimer, &QTimer::timeout, this, &Config::flushAsync);
m_reloadTimer.setSingleShot(true);
m_reloadTimer.setInterval(50);
connect(&m_reloadTimer, &QTimer::timeout, this, &Config::reloadAsync);
connectAutoSave(this);
connect(
&m_watcher,
&QFileSystemWatcher::directoryChanged,
this,
&Config::onWatcherEvent);
connect(
&m_watcher,
&QFileSystemWatcher::fileChanged,
this,
&Config::onWatcherEvent);
loadSync();
updateWatch();
m_firstLoadDone = true;
}
Config* Config::instance() {
return s_instance;
}
Config* Config::create(QQmlEngine*, QJSEngine*) {
if (!s_instance) s_instance = new Config();
return s_instance;
}
QString Config::filePath() const {
const QString dir =
QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) +
QStringLiteral("/zshell");
QDir().mkpath(dir);
return dir + QStringLiteral("/config.json");
}
void Config::loadSync() {
m_loading = true;
QFile f(filePath());
QJsonObject before;
bool existed = false;
if (f.open(QIODevice::ReadOnly)) {
const auto doc = QJsonDocument::fromJson(f.readAll());
if (doc.isObject()) {
before = doc.object();
existed = true;
} else {
qInfo() << "Config: existing config at" << filePath()
<< "is empty or not a valid JSON object - using defaults";
}
} else {
qInfo() << "Config: no existing config at" << filePath()
<< "- using defaults";
}
loadFromJson(QJsonValue(before));
m_loading = false;
const auto after = toJson().toObject();
if (!existed || after != before) saveNow();
}
void Config::updateWatch() {
const QFileInfo info(filePath());
const QString dir = info.absolutePath();
const QString file = info.absoluteFilePath();
if (!m_watcher.directories().contains(dir)) m_watcher.addPath(dir);
if (info.exists() && !m_watcher.files().contains(file))
m_watcher.addPath(file);
}
void Config::onWatcherEvent(const QString&) {
updateWatch();
const QFileInfo info(filePath());
if (!info.exists()) return;
QFile file(info.absoluteFilePath());
if (!file.open(QIODevice::ReadOnly)) return;
const QByteArray hash =
QCryptographicHash::hash(file.readAll(), QCryptographicHash::Sha256);
{
QMutexLocker lock(&m_writeMutex);
if (hash == m_lastWriteHash) return;
}
m_reloadPending = true;
if (!m_loading) m_reloadTimer.start();
}
void Config::loadAsync() {
m_loading = true;
auto future = QtConcurrent::run([this]() {
QFile f(filePath());
if (f.open(QIODevice::ReadOnly)) {
const auto doc = QJsonDocument::fromJson(f.readAll());
const bool valid = doc.isObject();
const QJsonObject before = valid ? doc.object() : QJsonObject();
QMetaObject::invokeMethod(
this,
[this, valid, before]() {
loadFromJson(QJsonValue(before));
m_loading = false;
const auto after = toJson().toObject();
if (!valid || after != before) saveNow();
if (m_reloadPending) m_reloadTimer.start();
},
Qt::QueuedConnection);
} else {
qInfo() << "Config: failed to reload from" << filePath()
<< "- using in-memory values";
QMetaObject::invokeMethod(
this,
[this]() {
m_loading = false;
if (m_reloadPending) m_reloadTimer.start();
},
Qt::QueuedConnection);
}
});
m_loadFuture = future;
}
void Config::load() {
if (!m_firstLoadDone) {
loadSync();
} else {
loadAsync();
}
}
void Config::reloadAsync() {
if (m_loading) return;
m_reloadPending = false;
loadAsync();
}
void Config::scheduleSave() {
if (m_loading) return;
m_saveTimer.start();
}
void Config::flushAsync() {
const QByteArray data =
QJsonDocument(toJson().toObject()).toJson(QJsonDocument::Indented);
const QByteArray hash =
QCryptographicHash::hash(data, QCryptographicHash::Sha256);
QThreadPool::globalInstance()->start([this, data, hash]() {
const bool success = writeAtomically(data);
QMetaObject::invokeMethod(
this,
[this, success, hash]() {
if (!success) return;
QMutexLocker lock(&m_writeMutex);
m_lastWriteHash = hash;
},
Qt::QueuedConnection);
});
}
void Config::saveNow() {
m_saveTimer.stop();
flushAsync();
}
void Config::connectAutoSave(ConfigNode* node) {
connect(node, &ConfigNode::propertiesChanged, this, [this] {
scheduleSave();
});
for (auto* child : node->childNodes())
connectAutoSave(child);
}
bool Config::writeAtomically(const QByteArray& data) {
QSaveFile f(filePath());
if (!f.open(QIODevice::WriteOnly)) {
qWarning() << "Config: failed to open for atomic write:"
<< f.errorString();
return false;
}
if (f.write(data) != data.size()) {
qWarning() << "Config: failed to write:" << f.errorString();
return false;
}
if (!f.commit()) {
qWarning() << "Config: commit failed:" << f.errorString();
return false;
}
return true;
}
}; // namespace ZShell::config
-114
View File
@@ -1,114 +0,0 @@
#pragma once
#include "configobject.hpp"
#include <QByteArray>
#include <QFileSystemWatcher>
#include <QFuture>
#include <QMutex>
#include <QTimer>
#include <qqmlregistration.h>
#include <QtConcurrent/QtConcurrent>
class QQmlEngine;
class QJSEngine;
namespace ZShell::config {
class Appearance;
class Background;
class Bar;
class Clipboard;
class Colors;
class Dashboard;
class Dock;
class General;
class Launcher;
class Llm;
class Lock;
class Notifs;
class Osd;
class Screenshot;
class Services;
class Sidebar;
class Utilities;
class Config : public ConfigObject {
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
Q_MOC_INCLUDE("appearance.hpp")
Q_MOC_INCLUDE("background.hpp")
Q_MOC_INCLUDE("bar.hpp")
Q_MOC_INCLUDE("clipboard.hpp")
Q_MOC_INCLUDE("colors.hpp")
Q_MOC_INCLUDE("dashboard.hpp")
Q_MOC_INCLUDE("dock.hpp")
Q_MOC_INCLUDE("general.hpp")
Q_MOC_INCLUDE("launcher.hpp")
Q_MOC_INCLUDE("llm.hpp")
Q_MOC_INCLUDE("lock.hpp")
Q_MOC_INCLUDE("notifs.hpp")
Q_MOC_INCLUDE("osd.hpp")
Q_MOC_INCLUDE("screenshot.hpp")
Q_MOC_INCLUDE("services.hpp")
Q_MOC_INCLUDE("sidebar.hpp")
Q_MOC_INCLUDE("utilities.hpp")
CONFIG_SUBOBJECT(Appearance, appearance)
CONFIG_SUBOBJECT(Background, background)
CONFIG_SUBOBJECT(Bar, bar)
CONFIG_SUBOBJECT(Clipboard, clipboard)
CONFIG_SUBOBJECT(Colors, colors)
CONFIG_SUBOBJECT(Dashboard, dashboard)
CONFIG_SUBOBJECT(Dock, dock)
CONFIG_SUBOBJECT(General, general)
CONFIG_SUBOBJECT(Launcher, launcher)
CONFIG_SUBOBJECT(Llm, llm)
CONFIG_SUBOBJECT(Lock, lock)
CONFIG_SUBOBJECT(Notifs, notifs)
CONFIG_SUBOBJECT(Osd, osd)
CONFIG_SUBOBJECT(Screenshot, screenshot)
CONFIG_SUBOBJECT(Services, services)
CONFIG_SUBOBJECT(Sidebar, sidebar)
CONFIG_SUBOBJECT(Utilities, utilities)
public:
explicit Config(QObject* parent = nullptr);
static Config* create(QQmlEngine*, QJSEngine*);
[[nodiscard]] static Config* instance();
Q_INVOKABLE void load();
Q_INVOKABLE void saveNow();
Q_INVOKABLE void reloadAsync();
private Q_SLOTS:
void scheduleSave();
void flushAsync();
void onWatcherEvent(const QString& path);
private:
QString filePath() const;
bool writeAtomically(const QByteArray& data);
void updateWatch();
void loadSync();
void loadAsync();
void connectAutoSave(ConfigNode* node);
QTimer m_saveTimer;
QTimer m_reloadTimer;
QFileSystemWatcher m_watcher;
QMutex m_writeMutex;
QByteArray m_lastWriteHash;
bool m_reloadPending = false;
bool m_loading = false;
bool m_firstLoadDone = false;
QFuture<void> m_loadFuture;
static Config* s_instance;
};
} // namespace ZShell::config
-314
View File
@@ -1,314 +0,0 @@
#include "configlist.hpp"
#include <QJsonObject>
#include <QMetaObject>
#include <QQmlEngine>
#include <QString>
namespace ZShell::config {
ConfigList::ConfigList(QObject* parent, const QVariantList& defaults)
: ConfigNode(parent), m_defaults(QJsonArray::fromVariantList(defaults)) {}
int ConfigList::count() const {
return static_cast<int>(m_items.size());
}
QVariantList ConfigList::values() const {
QVariantList vals;
vals.reserve(m_items.size());
for (auto* const item : m_items)
vals.append(QVariant::fromValue(item));
return vals;
}
ConfigObject* ConfigList::itemAt(int index) const {
if (index < 0 || index >= m_items.size()) return nullptr;
return m_items.at(index);
}
const QList<ConfigObject*>& ConfigList::items() const {
return m_items;
}
void ConfigList::remove(int index) {
if (index < 0 || index >= m_items.size()) {
qCWarning(lcConfig)
<< metaObject()->className() << "cannot remove index" << index
<< "of" << count();
return;
}
destroyItem(m_items.takeAt(index));
m_loaded = true;
emit countChanged();
emit valuesChanged();
notifyChanged();
}
void ConfigList::move(int from, int to) {
if (from < 0 || from >= m_items.size() || to < 0 || to >= m_items.size()) {
qCWarning(lcConfig) << metaObject()->className() << "cannot move"
<< from << "to" << to << "of" << count();
return;
}
if (from == to) return;
m_items.move(from, to);
m_loaded = true;
emit valuesChanged();
notifyChanged();
}
void ConfigList::clear() {
if (m_items.isEmpty()) return;
destroyItems();
m_loaded = true;
emit countChanged();
emit valuesChanged();
notifyChanged();
}
void ConfigList::loadFromJson(const QJsonValue& json) {
if (!json.isArray()) {
qCWarning(
lcConfig,
"Option '%s' must be a list, resetting to default",
qUtf8Printable(propertyPath()));
resetToDefaults();
materializeDefaults();
return;
}
populate(json.toArray());
m_loaded = true;
}
QJsonValue ConfigList::toJson() const {
if (m_loaded) return elementsToJson();
return QJsonValue::Undefined;
}
void ConfigList::clearLoadedKeys() {
m_loaded = false;
}
QStringList ConfigList::unknownKeys() const {
QStringList keys;
for (int i = 0; i < m_items.size(); ++i) {
const auto childKeys = m_items.at(i)->unknownKeys();
for (const auto& childKey : childKeys)
keys.append(joinPath(QString("[%1]").arg(i), childKey));
}
return keys;
}
void ConfigList::materializeDefaults() {
for (auto* const item : m_items)
item->materializeDefaults();
if (m_global) return;
m_loaded = true;
}
void ConfigList::resyncFromGlobal() {
syncValuesFromGlobal();
}
ConfigObject* ConfigList::insertItem(const QVariantMap& props, int index) {
const auto pos = index < 0 || index > m_items.size() ? m_items.size()
: index;
appendItem(QJsonObject::fromVariantMap(props));
m_items.move(m_items.size() - 1, pos);
auto* const item = m_items.at(pos);
const auto unknown = item->unknownKeys();
for (const auto& key : unknown)
qCWarning(lcConfig) << "Unknown option" << key << "for"
<< item->metaObject()->className();
m_loaded = true;
emit countChanged();
emit valuesChanged();
notifyChanged();
return item;
}
QJsonArray ConfigList::elementsToJson() const {
QJsonArray arr;
for (auto* const item : m_items)
arr.append(item->toJson().toObject());
return arr;
}
void ConfigList::resetToDefaults() {
loadFromJsonQuietly(m_defaults);
m_loaded = false;
}
void ConfigList::syncValuesFromGlobal() {
if (m_loaded) return;
if (auto* const global = qobject_cast<ConfigList*>(m_global))
populate(global->elementsToJson());
}
void ConfigList::onGlobalPropertiesChanged(const QMap<QString, QVariant>&) {
syncValuesFromGlobal();
}
QString ConfigList::childPath(const ConfigNode* child) const {
for (int i = 0; i < m_items.size(); ++i)
if (m_items.at(i) == child) return QString("[%1]").arg(i);
return {};
}
void ConfigList::populate(const QJsonArray& arr) {
const auto current = elementsToJson();
if (current == arr) return;
const auto old = m_items;
QList<bool> reused(old.size(), false);
m_items.clear();
m_items.reserve(arr.size());
for (int i = 0; i < arr.size(); ++i) {
auto match =
i < old.size() && !reused.at(i) && current.at(i) == arr.at(i) ? i
: -1;
for (int j = 0; match < 0 && j < old.size(); ++j)
if (!reused.at(j) && current.at(j) == arr.at(i)) match = j;
auto update = false;
for (int j = 0; match < 0 && j < old.size(); ++j) {
if (!reused.at(j) &&
sameElement(old.at(j), current.at(j), arr.at(i))) {
match = j;
update = true;
}
}
if (match < 0) {
appendItem(arr.at(i));
continue;
}
reused[match] = true;
if (update) old.at(match)->loadFromJsonQuietly(arr.at(i));
m_items.append(old.at(match));
}
for (int i = 0; i < old.size(); ++i)
if (!reused.at(i)) destroyItem(old.at(i));
if (m_items.size() != old.size()) emit countChanged();
emit valuesChanged();
notifyChanged();
}
bool ConfigList::sameElement(
const ConfigObject* item,
const QJsonValue& current,
const QJsonValue& json) {
const auto keys = item->identityKeys();
if (keys.isEmpty()) return false;
const auto currentObj = current.toObject();
const auto newObj = json.toObject();
if (currentObj.keys() != newObj.keys()) return false;
for (const auto& key : keys)
if (!newObj.contains(key) || currentObj.value(key) != newObj.value(key))
return false;
return true;
}
void ConfigList::appendItem(const QJsonValue& json) {
auto* const item = createItem();
QQmlEngine::setObjectOwnership(item, QQmlEngine::CppOwnership);
if (m_items.isEmpty()) rejectGlobalOnlyElement(item);
item->loadFromJsonQuietly(json);
connectItem(item);
m_items.append(item);
}
void ConfigList::rejectGlobalOnlyElement(const ConfigObject* item) const {
const auto keys = item->globalOnlyKeys();
if (keys.isEmpty()) return;
qCCritical(
lcConfig,
"%s declares global-only options (%s) which do not work inside a list, "
"use CONFIG_GLOBAL_LIST on %s instead",
item->metaObject()->className(),
qUtf8Printable(keys.join(", ")),
metaObject()->className());
}
void ConfigList::connectItem(ConfigNode* node) {
connect(
node, &ConfigNode::propertiesChanged, this, &ConfigList::onItemChanged);
const auto children = node->childNodes();
for (auto* const child : children)
connectItem(child);
}
void ConfigList::disconnectItem(ConfigNode* node) {
node->disconnect(this);
const auto children = node->childNodes();
for (auto* const child : children)
disconnectItem(child);
}
void ConfigList::destroyItems() {
for (auto* const item : std::as_const(m_items))
destroyItem(item);
m_items.clear();
}
void ConfigList::destroyItem(ConfigObject* item) {
disconnectItem(item);
item->deleteLater();
}
void ConfigList::onItemChanged() {
m_loaded = true;
notifyChanged();
}
void ConfigList::notifyChanged() {
notifyPropertyChanged("values", count());
}
} // namespace ZShell::config
-139
View File
@@ -1,139 +0,0 @@
#pragma once
#include "configobject.hpp"
#include <QJsonArray>
#include <QJsonObject>
#include <QQmlEngine>
#include <QVariantList>
namespace ZShell::config {
class ConfigList : public ConfigNode {
Q_OBJECT
QML_ANONYMOUS
Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(QVariantList values READ values NOTIFY valuesChanged)
public:
explicit ConfigList(
QObject* parent = nullptr, const QVariantList& defaults = {});
[[nodiscard]] int count() const;
[[nodiscard]] QVariantList values() const;
[[nodiscard]] ConfigObject* itemAt(int index) const;
[[nodiscard]] const QList<ConfigObject*>& items() const;
Q_INVOKABLE void remove(int index);
Q_INVOKABLE void move(int from, int to);
Q_INVOKABLE void clear();
void loadFromJson(const QJsonValue& json) override;
[[nodiscard]] QJsonValue toJson() const override;
void clearLoadedKeys() override;
[[nodiscard]] QStringList unknownKeys() const override;
void materializeDefaults() override;
void resyncFromGlobal() override;
signals:
void countChanged();
void valuesChanged();
protected:
virtual ConfigObject* createItem() = 0;
void resetToDefaults();
ConfigObject* insertItem(const QVariantMap& props, int index = -1);
[[nodiscard]] QJsonArray elementsToJson() const;
void syncValuesFromGlobal() override;
void onGlobalPropertiesChanged(
const QMap<QString, QVariant>& changed) override;
[[nodiscard]] QString childPath(const ConfigNode* child) const override;
private:
void populate(const QJsonArray& arr);
static bool sameElement(
const ConfigObject* item,
const QJsonValue& current,
const QJsonValue& json);
void appendItem(const QJsonValue& json);
void destroyItems();
void destroyItem(ConfigObject* item);
void rejectGlobalOnlyElement(const ConfigObject* item) const;
void connectItem(ConfigNode* node);
void disconnectItem(ConfigNode* node);
void onItemChanged();
void notifyChanged();
QJsonArray m_defaults;
QList<ConfigObject*> m_items;
bool m_loaded = false;
};
} // namespace ZShell::config
#define CONFIG_LIST_TYPE(Element, Name) \
class Name : public ZShell::config::ConfigList { \
Q_OBJECT \
QML_ANONYMOUS \
\
public: \
explicit Name( \
QObject* parent = nullptr, const QVariantList& defaults = {}) \
: ZShell::config::ConfigList(parent, defaults) { \
resetToDefaults(); \
} \
\
Q_INVOKABLE ZShell::config::Element* at(int index) const { \
return static_cast<ZShell::config::Element*>(itemAt(index)); \
} \
Q_INVOKABLE ZShell::config::Element* insert( \
const QVariantMap& props, int index = -1) { \
return static_cast<ZShell::config::Element*>( \
insertItem(props, index)); \
} \
\
protected: \
[[nodiscard]] ZShell::config::ConfigObject* createItem() override { \
return new ZShell::config::Element(this); \
} \
};
#define CONFIG_LIST(Type, name, ...) \
Q_PROPERTY(ZShell::config::Type* name READ name CONSTANT) \
\
public: \
[[nodiscard]] Type* name() const { \
return m_##name; \
} \
\
private: \
Type* m_##name = new Type(this __VA_OPT__(, __VA_ARGS__));
#define LIST_ENTRY(id, enabled) \
vmap({{"id", QString::fromUtf8(#id)}, {"enabled", enabled}})
namespace ZShell::config {
class ListEntry : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
CFG_PROPERTY(QString, id)
CFG_PROPERTY(bool, enabled, true)
public:
explicit ListEntry(QObject* parent = nullptr) : ConfigObject(parent) {}
[[nodiscard]] QStringList identityKeys() const override {
return {QStringLiteral("id")};
}
};
CONFIG_LIST_TYPE(ListEntry, EntryList)
} // namespace ZShell::config
-89
View File
@@ -1,89 +0,0 @@
#include "confignode.hpp"
#include <qjsonvalue.h>
#include <qloggingcategory.h>
#include <qmetaobject.h>
#include <qstring.h>
namespace ZShell::config {
Q_LOGGING_CATEGORY(lcConfig, "zshell.config")
ConfigNode::ConfigNode(QObject* parent) : QObject(parent) {
m_batchTimer = new QTimer(this);
m_batchTimer->setSingleShot(true);
m_batchTimer->setInterval(0);
connect(
m_batchTimer, &QTimer::timeout, this, &ConfigNode::emitBatchedChanges);
}
void ConfigNode::loadFromJsonQuietly(const QJsonValue& json) {
setNotifySuppressed(true);
loadFromJson(json);
setNotifySuppressed(false);
}
void ConfigNode::setNotifySuppressed(bool suppressed) {
m_suppressNotify = suppressed;
const auto children = childNodes();
for (auto* const child : children)
child->setNotifySuppressed(suppressed);
}
QList<ConfigNode*> ConfigNode::childNodes() const {
return {};
}
void ConfigNode::syncFromGlobal(ConfigNode* global) {
m_global = global;
resyncFromGlobal();
}
bool ConfigNode::isOverlay() const {
return m_global != nullptr && this != m_global;
}
QString ConfigNode::propertyPath(const QString& name) const {
if (name.isEmpty()) return objectName();
QObject* const parentObj = parent();
if (parentObj) {
if (auto* parentNode = qobject_cast<ConfigNode*>(parentObj)) {
const auto childName = parentNode->childPath(this);
if (!childName.isEmpty()) {
return joinPath(parentNode->propertyPath(childName), name);
}
}
}
return name;
}
int ConfigNode::basePropertyOffset() {
return QObject::staticMetaObject.propertyCount();
}
void ConfigNode::notifyPropertyChanged(
const QString& name, const QVariant& value) {
if (m_suppressNotify) return;
m_pendingChanges[name] = value;
if (m_batchTimer && !m_batchTimer->isActive()) m_batchTimer->start();
}
QString ConfigNode::joinPath(const QString& parent, const QString& child) {
// List elements are addressed as parent[i], everything else as parent.child
if (child.startsWith(QLatin1Char('['))) return parent + child;
return parent + QLatin1Char('.') + child;
}
void ConfigNode::emitBatchedChanges() {
if (m_pendingChanges.isEmpty()) return;
QMap<QString, QVariant> changes = m_pendingChanges;
m_pendingChanges.clear();
emit propertiesChanged(changes);
}
} // namespace ZShell::config
-65
View File
@@ -1,65 +0,0 @@
#pragma once
#include <QJsonValue>
#include <QLoggingCategory>
#include <QMap>
#include <QObject>
#include <QStringList>
#include <QTimer>
#include <QVariant>
namespace ZShell::config {
Q_DECLARE_LOGGING_CATEGORY(lcConfig)
class ConfigNode : public QObject {
Q_OBJECT
public:
explicit ConfigNode(QObject* parent = nullptr);
virtual void loadFromJson(const QJsonValue& json) = 0;
void loadFromJsonQuietly(const QJsonValue& json);
[[nodiscard]] virtual QJsonValue toJson() const = 0;
virtual void clearLoadedKeys() = 0;
[[nodiscard]] virtual QStringList unknownKeys() const = 0;
[[nodiscard]] virtual QList<ConfigNode*> childNodes() const;
virtual void materializeDefaults() = 0;
void syncFromGlobal(ConfigNode* global);
virtual void resyncFromGlobal() = 0;
[[nodiscard]] bool isOverlay() const;
[[nodiscard]] QString propertyPath(const QString& name = {}) const;
[[nodiscard]] static int basePropertyOffset();
signals:
void propertiesChanged(const QMap<QString, QVariant>& changed);
protected:
virtual void syncValuesFromGlobal() = 0;
virtual void onGlobalPropertiesChanged(
const QMap<QString, QVariant>& changed) = 0;
[[nodiscard]] virtual QString childPath(const ConfigNode* child) const = 0;
void notifyPropertyChanged(const QString& name, const QVariant& value);
[[nodiscard]] static QString joinPath(
const QString& parent, const QString& child);
ConfigNode* m_global = nullptr;
private:
void setNotifySuppressed(bool suppressed);
void emitBatchedChanges();
QMap<QString, QVariant> m_pendingChanges;
QTimer* m_batchTimer = nullptr;
bool m_suppressNotify = false;
};
} // namespace ZShell::config
-344
View File
@@ -1,344 +0,0 @@
#include "configobject.hpp"
#include <QJsonArray>
#include <QMetaObject>
#include <QStringList>
#include <QVariant>
namespace ZShell::config {
namespace {
bool isStringArray(const QJsonArray& arr) {
for (const auto& v : arr) {
if (!v.isString()) return false;
}
return true;
}
bool jsonValueMatchesType(const QJsonValue& val, QMetaType type) {
switch (val.type()) {
case QJsonValue::Bool:
return type.id() == QMetaType::Bool;
case QJsonValue::Double:
switch (type.id()) {
case QMetaType::Int:
case QMetaType::UInt:
case QMetaType::LongLong:
case QMetaType::ULongLong:
case QMetaType::Double:
case QMetaType::Float:
return true;
default:
return false;
}
case QJsonValue::String:
return type.id() == QMetaType::QString;
case QJsonValue::Array:
if (type.id() == QMetaType::QStringList)
return isStringArray(val.toArray());
return type.id() == QMetaType::QVariantList;
case QJsonValue::Object:
case QJsonValue::Null:
case QJsonValue::Undefined:
default:
return false;
}
}
} // namespace
ConfigObject::ConfigObject(QObject* parent) : ConfigNode(parent) {}
void ConfigObject::loadFromJson(const QJsonValue& json) {
const auto obj = json.toObject();
const auto* meta = metaObject();
QSet<QString> known;
QSet<QString> invalid;
for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) {
auto prop = meta->property(i);
const auto key = QString::fromUtf8(prop.name());
known.insert(key);
if (!obj.contains(key)) continue;
const auto jsonVal = obj.value(key);
if (prop.metaType().flags().testFlag(QMetaType::PointerToQObject)) {
if (auto* const node = prop.read(this).value<ConfigNode*>()) {
node->loadFromJson(jsonVal);
continue;
}
}
if (!prop.isWritable()) continue;
if (!jsonValueMatchesType(jsonVal, prop.metaType())) {
qWarning() << "Config: type mismatch for" << key << "in"
<< meta->className() << "- expected"
<< prop.metaType().name() << "got value" << jsonVal
<< "- resetting to default";
invalid.insert(key);
continue;
}
if (prop.metaType().id() == QMetaType::QStringList) {
QStringList list;
const auto jsonArr = jsonVal.toArray();
for (const auto& v : jsonArr)
list.append(v.toString());
prop.write(this, QVariant::fromValue(list));
m_loadedKeys.insert(key);
continue;
}
if (prop.metaType().id() == QMetaType::QVariantList) {
prop.write(this, jsonVal.toVariant());
m_loadedKeys.insert(key);
continue;
}
prop.write(this, jsonVal.toVariant());
m_loadedKeys.insert(key);
}
m_extras = {};
for (auto it = obj.begin(); it != obj.end(); ++it) {
if (!known.contains(it.key()) || invalid.contains(it.key())) {
m_extras.insert(it.key(), it.value());
}
}
materializeDefaults();
}
QJsonValue ConfigObject::toJson() const {
QJsonObject obj;
const auto* meta = metaObject();
for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) {
const auto prop = meta->property(i);
if (!prop.isReadable()) continue;
const auto key = QString::fromUtf8(prop.name());
const auto value = prop.read(this);
if (prop.metaType().flags().testFlag(QMetaType::PointerToQObject)) {
if (auto* const node = value.value<ConfigNode*>()) {
const auto childJson = node->toJson();
if (!childJson.isUndefined()) obj.insert(key, childJson);
continue;
}
}
if (!m_loadedKeys.contains(key)) continue;
if (!prop.isWritable()) continue;
if (prop.metaType().id() == QMetaType::QStringList) {
QJsonArray arr;
const auto strList = value.toStringList();
for (const auto& s : strList)
arr.append(s);
obj.insert(key, arr);
continue;
}
if (prop.metaType().id() == QMetaType::QVariantList) {
obj.insert(key, QJsonArray::fromVariantList(value.toList()));
continue;
}
obj.insert(key, QJsonValue::fromVariant(value));
}
if (obj.isEmpty()) return QJsonValue::Undefined;
return obj;
}
void ConfigObject::clearLoadedKeys() {
m_loadedKeys.clear();
m_extras = {};
const auto* meta = metaObject();
for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) {
auto prop = meta->property(i);
if (auto* const node = prop.read(this).value<ConfigNode*>())
node->clearLoadedKeys();
}
}
QStringList ConfigObject::identityKeys() const {
return {};
}
QStringList ConfigObject::unknownKeys() const {
auto keys = m_extras.keys();
const auto* meta = metaObject();
for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) {
const auto prop = meta->property(i);
auto* const node = prop.read(this).value<ConfigNode*>();
if (!node) continue;
const auto childKeys = node->unknownKeys();
for (const auto& childKey : childKeys)
keys.append(joinPath(QString::fromUtf8(prop.name()), childKey));
}
return keys;
}
QList<ConfigNode*> ConfigObject::childNodes() const {
QList<ConfigNode*> nodes;
const auto* meta = metaObject();
for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) {
const auto prop = meta->property(i);
if (auto* const node = prop.read(this).value<ConfigNode*>())
nodes.append(node);
}
return nodes;
}
void ConfigObject::materializeDefaults() {
const auto* meta = metaObject();
for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) {
const auto prop = meta->property(i);
const auto key = QString::fromUtf8(prop.name());
if (auto* const node = prop.read(this).value<ConfigNode*>()) {
node->materializeDefaults();
continue;
}
if (!prop.isWritable()) continue;
if (m_global) continue;
m_loadedKeys.insert(key);
}
}
void ConfigObject::syncValuesFromGlobal() {
const auto* meta = metaObject();
for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) {
auto prop = meta->property(i);
const auto key = QString::fromUtf8(prop.name());
if (auto* const node = prop.read(this).value<ConfigNode*>()) {
if (auto* const globalNode =
prop.read(m_global).value<ConfigNode*>())
node->syncFromGlobal(globalNode);
continue;
}
if (!prop.isWritable()) continue;
if (!m_loadedKeys.contains(key)) {
auto val = prop.read(m_global);
prop.write(this, val);
m_loadedKeys.remove(key);
}
}
}
void ConfigObject::resyncFromGlobal() {
if (!m_global) return;
const auto* meta = metaObject();
for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) {
auto prop = meta->property(i);
const auto key = QString::fromUtf8(prop.name());
if (auto* const node = prop.read(this).value<ConfigNode*>()) {
node->resyncFromGlobal();
continue;
}
if (!prop.isWritable()) continue;
if (!m_loadedKeys.contains(key)) {
prop.write(this, prop.read(m_global));
m_loadedKeys.remove(key);
}
}
}
bool ConfigObject::isPropertyLoaded(const QString& name) const {
return m_loadedKeys.contains(name);
}
bool ConfigObject::isGlobalOnly(const QString& name) const {
return m_globalOnlyKeys.contains(name);
}
QStringList ConfigObject::globalOnlyKeys() const {
return {m_globalOnlyKeys.begin(), m_globalOnlyKeys.end()};
}
void ConfigObject::resetOption(const QString& name) {
m_loadedKeys.remove(name);
const int idx = metaObject()->indexOfProperty(name.toUtf8().constData());
if (idx < 0) return;
const auto prop = metaObject()->property(idx);
if (auto* const node = prop.read(this).value<ConfigNode*>()) {
node->clearLoadedKeys();
node->resyncFromGlobal();
return;
}
if (m_global && prop.isWritable()) prop.write(this, prop.read(m_global));
}
void ConfigObject::onGlobalPropertiesChanged(
const QMap<QString, QVariant>& changed) {
for (auto it = changed.begin(); it != changed.end(); ++it) {
if (m_loadedKeys.contains(it.key()) || isGlobalOnly(it.key())) continue;
int idx = metaObject()->indexOfProperty(it.key().toUtf8().constData());
if (idx >= 0) {
metaObject()->property(idx).write(this, it.value());
m_loadedKeys.remove(it.key());
}
}
}
QString ConfigObject::childPath(const ConfigNode* child) const {
const auto* meta = metaObject();
for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) {
const auto prop = meta->property(i);
if (prop.read(this).value<ConfigNode*>() == child)
return QString::fromUtf8(prop.name());
}
return {};
}
void ConfigObject::markPropertyLoaded(const QString& name) {
m_loadedKeys.insert(name);
}
void ConfigObject::markGlobalOnly(const QString& name) {
m_globalOnlyKeys.insert(name);
}
} // namespace ZShell::config
-101
View File
@@ -1,101 +0,0 @@
#pragma once
#include "confignode.hpp"
#include <QJsonObject>
#include <QMetaProperty>
#include <QMetaType>
#include <QObject>
#include <QSet>
#include <QStringList>
#include <QVariant>
#include <QVariantList>
namespace ZShell::config {
inline QVariantMap vmap(
std::initializer_list<std::pair<QString, QVariant>> entries) {
QVariantMap map;
for (const auto& [key, value] : entries)
map.insert(key, value);
return map;
}
#define CFG_PROPERTY(Type, name, ...) \
Q_PROPERTY(Type name READ name WRITE set_##name NOTIFY name##Changed) \
public: \
[[nodiscard]] Type name() const { \
return m_##name; \
} \
void set_##name(const Type& value) { \
if (ConfigObject::updateMember(m_##name, value)) { \
markPropertyLoaded(QStringLiteral(#name)); \
Q_EMIT name##Changed(); \
notifyPropertyChanged( \
QStringLiteral(#name), QVariant::fromValue(m_##name)); \
} \
} \
Q_SIGNAL void name##Changed(); \
\
private: \
Type m_##name __VA_OPT__(= __VA_ARGS__);
#define CONFIG_SUBOBJECT(Type, name) \
Q_PROPERTY(ZShell::config::Type* name READ name CONSTANT) \
\
public: \
[[nodiscard]] Type* name() const { \
return m_##name; \
} \
\
private: \
Type* m_##name = nullptr;
class ConfigObject : public ConfigNode {
Q_OBJECT
public:
explicit ConfigObject(QObject* parent = nullptr);
void loadFromJson(const QJsonValue& json) override;
[[nodiscard]] QJsonValue toJson() const override;
void clearLoadedKeys() override;
[[nodiscard]] QStringList unknownKeys() const override;
[[nodiscard]] QList<ConfigNode*> childNodes() const override;
void materializeDefaults() override;
void resyncFromGlobal() override;
[[nodiscard]] virtual QStringList identityKeys() const;
[[nodiscard]] bool isPropertyLoaded(const QString& name) const;
[[nodiscard]] bool isGlobalOnly(const QString& name) const;
[[nodiscard]] QStringList globalOnlyKeys() const;
Q_INVOKABLE void resetOption(const QString& name);
template <typename T> static bool updateMember(T& member, const T& value) {
if constexpr (std::is_floating_point_v<T>) {
if (qFuzzyCompare(member + 1.0, value + 1.0)) return false;
} else {
if (member == value) return false;
}
member = value;
return true;
}
protected:
void syncValuesFromGlobal() override;
void onGlobalPropertiesChanged(
const QMap<QString, QVariant>& changed) override;
[[nodiscard]] QString childPath(const ConfigNode* child) const override;
void markPropertyLoaded(const QString& name);
void markGlobalOnly(const QString& name);
private:
QSet<QString> m_loadedKeys;
QSet<QString> m_globalOnlyKeys;
QJsonObject m_extras;
};
} // namespace ZShell::config
+37 -47
View File
@@ -1,63 +1,53 @@
#pragma once
#include "configobject.hpp"
#include "common.hpp"
#include <qqmlintegration.h>
namespace ZShell::config {
class Performance : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Performance : public settings::ObjectNode {
CONFIG_NODE(Performance, settings::ObjectNode)
CFG_PROPERTY(bool, enabled, true)
CFG_PROPERTY(bool, showBattery, false)
CFG_PROPERTY(bool, showCpu, true)
CFG_PROPERTY(bool, showGpu, true)
CFG_PROPERTY(bool, showMemory, true)
CFG_PROPERTY(bool, showNetwork, true)
CFG_PROPERTY(bool, showStorage, true)
CFG_PROPERTY(bool, showVram, true)
public:
explicit Performance(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(bool, enabled, true)
CONFIG_PROPERTY(bool, showBattery, false)
CONFIG_PROPERTY(bool, showCpu, true)
CONFIG_PROPERTY(bool, showGpu, true)
CONFIG_PROPERTY(bool, showMemory, true)
CONFIG_PROPERTY(bool, showNetwork, true)
CONFIG_PROPERTY(bool, showStorage, true)
CONFIG_PROPERTY(bool, showVram, true)
};
class DashboardSizes : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class ProcessColumnEntry : public settings::ObjectNode {
CONFIG_NODE(ProcessColumnEntry, settings::ObjectNode)
CFG_PROPERTY(int, dateTimeWidth, 110)
CFG_PROPERTY(int, infoIconSize, 25)
CFG_PROPERTY(int, infoWidth, 200)
CFG_PROPERTY(int, mediaCoverArtSize, 150)
CFG_PROPERTY(int, mediaProgressSweep, 180)
CFG_PROPERTY(int, mediaProgressThickness, 8)
CFG_PROPERTY(int, mediaVisualizerSize, 200)
CFG_PROPERTY(int, mediaWidth, 200)
CFG_PROPERTY(int, resourceProgessThickness, 10)
CFG_PROPERTY(int, resourceSize, 200)
CFG_PROPERTY(int, tabIndicatorHeight, 3)
CFG_PROPERTY(int, tabIndicatorSpacing, 5)
CFG_PROPERTY(int, weatherWidth, 250)
public:
explicit DashboardSizes(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(QString, id, {})
CONFIG_PROPERTY(double, width, 0)
};
class Dashboard : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
CONFIG_LIST_TYPE(ProcessColumnEntry, ProcessColumnList)
CFG_PROPERTY(bool, enabled, true)
CFG_PROPERTY(int, mediaUpdateInterval, 2000)
class Dashboard : public settings::ObjectNode {
CONFIG_NODE(Dashboard, settings::ObjectNode)
CONFIG_PROPERTY(bool, enabled, true)
CONFIG_PROPERTY(bool, showOnHover, true)
CONFIG_PROPERTY(int, dragThreshold, 50)
CONFIG_PROPERTY(int, mediaUpdateInterval, 2000)
CONFIG_PROPERTY(bool, showWeather, true)
CONFIG_PROPERTY(bool, showDashboard, true)
CONFIG_PROPERTY(bool, showMedia, true)
CONFIG_PROPERTY(bool, showProcesses, false)
CONFIG_SUBOBJECT(Performance, performance)
CFG_PROPERTY(int, resourceUpdateInterval, 1000)
CONFIG_SUBOBJECT(DashboardSizes, sizes)
public:
explicit Dashboard(QObject* parent = nullptr)
: ConfigObject(parent)
, m_performance(new Performance(this))
, m_sizes(new DashboardSizes(this)) {}
CONFIG_PROPERTY(int, resourceUpdateInterval, 1000)
CONFIG_LIST(
ProcessColumnList,
processColumns,
DEFAULT_ARG({
settings::vmap({{"id", "command"}}),
settings::vmap({{"id", "pid"}}),
settings::vmap({{"id", "cpuUsage"}}),
settings::vmap({{"id", "memoryUsage"}}),
}))
};
} // namespace ZShell::config
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "common.hpp"
#include <qqmlintegration.h>
namespace ZShell::config {
class Nightlight : public settings::ObjectNode {
CONFIG_NODE(Nightlight, settings::ObjectNode)
CONFIG_PROPERTY(bool, schedule, true)
CONFIG_PROPERTY(int, scheduleStart, 1200)
CONFIG_PROPERTY(int, scheduleEnd, 570)
CONFIG_PROPERTY(bool, useNative, true)
CONFIG_PROPERTY(int, fadeDuration, 2000)
CONFIG_PROPERTY(int, temp, 2600)
};
class Display : public settings::ObjectNode {
CONFIG_NODE(Display, settings::ObjectNode)
CONFIG_SUBOBJECT(Nightlight, nightlight)
};
} // namespace ZShell::config
+9 -13
View File
@@ -1,29 +1,25 @@
#pragma once
#include "configobject.hpp"
#include "common.hpp"
#include <QStringList>
#include <qqmlintegration.h>
namespace ZShell::config {
class Dock : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Dock : public settings::ObjectNode {
CONFIG_NODE(Dock, settings::ObjectNode)
CFG_PROPERTY(bool, enable, false)
CFG_PROPERTY(int, height, 80)
CFG_PROPERTY(bool, hoverToReveal, false)
CFG_PROPERTY(QStringList, ignoredAppRegexes, {})
CFG_PROPERTY(
CONFIG_PROPERTY(bool, enable, false)
CONFIG_PROPERTY(int, height, 80)
CONFIG_PROPERTY(bool, hoverToReveal, false)
CONFIG_PROPERTY(QStringList, ignoredAppRegexes, {})
CONFIG_PROPERTY(
QStringList,
pinnedApps,
(QStringList{
QStringLiteral("com.ayugram.desktop"),
QStringLiteral("com.obsproject.studio"),
QStringLiteral("librewolf")}))
CFG_PROPERTY(bool, pinnedOnStartup, false)
public:
explicit Dock(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(bool, pinnedOnStartup, false)
};
} // namespace ZShell::config
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include <qobject.h>
#include <qqmlintegration.h>
namespace ZShell::config {
#define ENUM(Name, ...) \
namespace Name { \
\
Q_NAMESPACE \
QML_ELEMENT \
\
enum Enum : quint8 { __VA_ARGS__ }; \
Q_ENUM_NS(Enum) \
};
ENUM(BarWorkspaceDisplay, Shapes, Text, Icons)
ENUM(BarWorkspaceCapitalisation, Preserve, Upper, Lower)
ENUM(LyricsBackend, Auto, Local, LRCLIB, NetEase)
ENUM(GpuType, Auto, Nvidia, Generic, None)
ENUM(NotifsFullscreen, On, Off)
ENUM(TemperatureUnit, Auto, Celsius, Fahrenheit, Kelvin)
ENUM(DataUnit, Binary, Decimal)
ENUM(ClockFormat, Auto, TwelveHour, TwentyFourHour)
#undef ENUM
} // namespace ZShell::config
+288
View File
@@ -0,0 +1,288 @@
#include "font.hpp"
#include "appearance.hpp"
#include "rootnodes.hpp"
namespace ZShell::config {
FontStyleBase::FontStyleBase(QObject* parent) : QObject(parent) {}
QFont FontStyleBase::large() const {
return m_large;
}
QFont FontStyleBase::medium() const {
return m_medium;
}
QFont FontStyleBase::small() const {
return m_small;
}
QFont FontStyleBase::buildFont(
const FontSizeNode* cfg, const QString& fallbackFamily, qreal scale) {
const auto family = cfg->property("family").toString();
const auto size = cfg->property("size").toInt();
const auto weight = cfg->property("weight").value<QFont::Weight>();
const auto italic = cfg->property("italic").toBool();
const auto vaxes = cfg->property("vaxes").toMap();
QFont font;
font.setFamily(family.isEmpty() ? fallbackFamily : family);
const int scaledSize = static_cast<int>(size * scale);
const int cappedSize = scaledSize > 0 ? scaledSize : 1;
font.setPointSize(cappedSize);
font.setVariableAxis("opsz", static_cast<float>(cappedSize));
font.setWeight(weight);
font.setVariableAxis("wght", static_cast<float>(weight));
font.setItalic(italic);
for (auto it = vaxes.constBegin(); it != vaxes.constEnd(); ++it) {
if (auto tag = QFont::Tag::fromString(it.key()))
font.setVariableAxis(*tag, it.value().toFloat());
}
return font;
}
void FontStyleBase::bind(FontStyleNode* cfg) {
if (m_cfg == cfg) return;
if (m_cfg) {
disconnect(m_cfg, nullptr, this, nullptr);
disconnect(m_cfg->largeNode(), nullptr, this, nullptr);
disconnect(m_cfg->mediumNode(), nullptr, this, nullptr);
disconnect(m_cfg->smallNode(), nullptr, this, nullptr);
}
m_cfg = cfg;
if (cfg) {
connect(
cfg, &settings::Node::optionChanged, this, &FontStyleBase::rebuild);
connect(
cfg->largeNode(),
&settings::Node::optionChanged,
this,
&FontStyleBase::rebuild);
connect(
cfg->mediumNode(),
&settings::Node::optionChanged,
this,
&FontStyleBase::rebuild);
connect(
cfg->smallNode(),
&settings::Node::optionChanged,
this,
&FontStyleBase::rebuild);
}
rebuild();
}
void FontStyleBase::rebuild() {
if (m_cfg) {
const auto family = m_cfg->property("family").toString();
m_large = buildFont(m_cfg->largeNode(), family, m_scale);
m_medium = buildFont(m_cfg->mediumNode(), family, m_scale);
m_small = buildFont(m_cfg->smallNode(), family, m_scale);
} else {
m_large = QFont();
m_medium = QFont();
m_small = QFont();
}
emit fontsChanged();
}
void FontStyleBase::setScale(qreal scale) {
if (qFuzzyCompare(m_scale + 1.0, scale + 1.0)) return;
m_scale = scale;
rebuild();
}
FontStyle::FontStyle(QObject* parent)
: FontStyleBase(parent), m_builders(new FontBuilders(this, this)) {}
FontBuilders* FontStyle::builders() const {
return m_builders;
}
IconFontStyle::IconFontStyle(QObject* parent)
: FontStyleBase(parent), m_builders(new IconFontBuilders(this, this)) {}
FontBuilder IconFontStyle::size(int pointSize) {
return FontBuilder(m_small).size(pointSize);
}
FontBuilder IconFontStyle::pixelSize(int pixelSize) {
return FontBuilder(m_small).pixelSize(pixelSize);
}
void IconFontStyle::bind(FontStyleNode* cfg) {
if (m_cfg == cfg) return;
if (m_cfg) disconnect(m_cfg->extraLargeNode(), nullptr, this, nullptr);
FontStyleBase::bind(cfg);
if (cfg)
connect(
cfg->extraLargeNode(),
&settings::Node::optionChanged,
this,
&IconFontStyle::rebuild);
}
QFont IconFontStyle::extraLarge() const {
return m_extraLarge;
}
IconFontBuilders* IconFontStyle::builders() const {
return m_builders;
}
void IconFontStyle::rebuild() {
if (m_cfg) {
const auto family = m_cfg->property("family").toString();
m_extraLarge = buildFont(m_cfg->extraLargeNode(), family, m_scale);
} else {
m_extraLarge = QFont();
}
FontStyleBase::rebuild();
}
FontBuilders::FontBuilders(const FontStyleBase* style, QObject* parent)
: QObject(parent), m_style(style) {
connect(
style,
&FontStyleBase::fontsChanged,
this,
&FontBuilders::buildersChanged);
}
FontBuilder FontBuilders::large() const {
return FontBuilder(m_style->large());
}
FontBuilder FontBuilders::medium() const {
return FontBuilder(m_style->medium());
}
FontBuilder FontBuilders::small() const {
return FontBuilder(m_style->small());
}
IconFontBuilders::IconFontBuilders(const IconFontStyle* style, QObject* parent)
: FontBuilders(style, parent) {}
FontBuilder IconFontBuilders::extraLarge() const {
return FontBuilder(
static_cast<const IconFontStyle*>(m_style)->extraLarge());
}
FontTokens::FontTokens(QObject* parent)
: QObject(parent)
, m_headline(new FontStyle(this))
, m_title(new FontStyle(this))
, m_body(new FontStyle(this))
, m_label(new FontStyle(this))
, m_mono(new FontStyle(this))
, m_icon(new IconFontStyle(this)) {
bindFont(ConfigSingleton::instance()->appearance()->font());
}
FontStyle* FontTokens::headline() const {
return m_headline;
}
FontStyle* FontTokens::title() const {
return m_title;
}
FontStyle* FontTokens::body() const {
return m_body;
}
FontStyle* FontTokens::label() const {
return m_label;
}
FontStyle* FontTokens::mono() const {
return m_mono;
}
IconFontStyle* FontTokens::icon() const {
return m_icon;
}
FontBuilder FontTokens::clock() const {
return FontBuilder(m_clock);
}
QString FontTokens::workspaces() const {
return m_font ? m_font->workspaces() : QString();
}
void FontTokens::bindFont(AppearanceFont* font) {
if (m_font == font) return;
if (m_font) disconnect(m_font, nullptr, this, nullptr);
m_font = font;
if (font) {
rebuildScale();
m_headline->bind(font->headline());
m_title->bind(font->title());
m_body->bind(font->body());
m_label->bind(font->label());
m_mono->bind(font->mono());
m_icon->bind(font->icon());
connect(
font,
&AppearanceFont::clockChanged,
this,
&FontTokens::rebuildClock);
connect(
font,
&AppearanceFont::scaleChanged,
this,
&FontTokens::rebuildScale);
connect(
font,
&AppearanceFont::workspacesChanged,
this,
&FontTokens::workspacesChanged);
} else {
rebuildScale();
m_headline->bind(nullptr);
m_title->bind(nullptr);
m_body->bind(nullptr);
m_label->bind(nullptr);
m_mono->bind(nullptr);
m_icon->bind(nullptr);
}
rebuildClock();
emit workspacesChanged();
}
void FontTokens::rebuildScale() {
const qreal s = m_font ? m_font->scale() : 1;
m_headline->setScale(s);
m_title->setScale(s);
m_body->setScale(s);
m_label->setScale(s);
m_mono->setScale(s);
m_icon->setScale(s);
}
void FontTokens::rebuildClock() {
QFont f;
if (m_font) f.setFamily(m_font->clock());
m_clock = f;
emit clockChanged();
}
} // namespace ZShell::config
+182
View File
@@ -0,0 +1,182 @@
#pragma once
#include "fontbuilder.hpp"
#include <qfont.h>
#include <qobject.h>
#include <qqmlintegration.h>
#include <qvariantmap.h>
namespace ZShell::config {
class AppearanceFont;
class FontBuilders;
class FontStyleBase;
class FontSizeNode;
class FontStyleNode;
class IconFontStyle;
class FontBuilders : public QObject {
Q_OBJECT
QML_ANONYMOUS
Q_PROPERTY(
ZShell::config::FontBuilder large READ large NOTIFY buildersChanged
FINAL)
Q_PROPERTY(
ZShell::config::FontBuilder medium READ medium NOTIFY buildersChanged
FINAL)
Q_PROPERTY(
ZShell::config::FontBuilder small READ small NOTIFY buildersChanged
FINAL)
public:
explicit FontBuilders(const FontStyleBase* style, QObject* parent = nullptr);
[[nodiscard]] FontBuilder large() const;
[[nodiscard]] FontBuilder medium() const;
[[nodiscard]] FontBuilder small() const;
signals:
void buildersChanged();
protected:
const FontStyleBase* m_style;
};
class IconFontBuilders : public FontBuilders {
Q_OBJECT
QML_ANONYMOUS
Q_PROPERTY(
ZShell::config::FontBuilder extraLarge READ extraLarge NOTIFY
buildersChanged FINAL)
public:
explicit IconFontBuilders(
const IconFontStyle* style, QObject* parent = nullptr);
[[nodiscard]] FontBuilder extraLarge() const;
};
class FontStyleBase : public QObject {
Q_OBJECT
Q_PROPERTY(QFont large READ large NOTIFY fontsChanged FINAL)
Q_PROPERTY(QFont medium READ medium NOTIFY fontsChanged FINAL)
Q_PROPERTY(QFont small READ small NOTIFY fontsChanged FINAL)
public:
explicit FontStyleBase(QObject* parent = nullptr);
virtual void bind(FontStyleNode* cfg);
void setScale(qreal scale);
[[nodiscard]] QFont large() const;
[[nodiscard]] QFont medium() const;
[[nodiscard]] QFont small() const;
signals:
void fontsChanged();
protected:
virtual void rebuild();
static QFont buildFont(
const FontSizeNode* cfg, const QString& fallbackFamily, qreal scale);
FontStyleNode* m_cfg = nullptr;
qreal m_scale = 1;
QFont m_large;
QFont m_medium;
QFont m_small;
};
class FontStyle : public FontStyleBase {
Q_OBJECT
QML_ANONYMOUS
Q_PROPERTY(FontBuilders* builders READ builders CONSTANT FINAL)
public:
explicit FontStyle(QObject* parent = nullptr);
[[nodiscard]] FontBuilders* builders() const;
private:
FontBuilders* m_builders;
};
class IconFontStyle : public FontStyleBase {
Q_OBJECT
QML_ANONYMOUS
Q_PROPERTY(QFont extraLarge READ extraLarge NOTIFY fontsChanged FINAL)
Q_PROPERTY(IconFontBuilders* builders READ builders CONSTANT FINAL)
public:
explicit IconFontStyle(QObject* parent = nullptr);
Q_INVOKABLE FontBuilder size(int pointSize);
Q_INVOKABLE FontBuilder pixelSize(int pixelSize);
void bind(FontStyleNode* cfg) override;
[[nodiscard]] QFont extraLarge() const;
[[nodiscard]] IconFontBuilders* builders() const;
protected:
void rebuild() override;
private:
QFont m_extraLarge;
IconFontBuilders* m_builders;
};
class FontTokens : public QObject {
Q_OBJECT
QML_ANONYMOUS
Q_PROPERTY(FontStyle* headline READ headline CONSTANT FINAL)
Q_PROPERTY(FontStyle* title READ title CONSTANT FINAL)
Q_PROPERTY(FontStyle* body READ body CONSTANT FINAL)
Q_PROPERTY(FontStyle* label READ label CONSTANT FINAL)
Q_PROPERTY(FontStyle* mono READ mono CONSTANT FINAL)
Q_PROPERTY(IconFontStyle* icon READ icon CONSTANT FINAL)
Q_PROPERTY(
ZShell::config::FontBuilder clock READ clock NOTIFY clockChanged FINAL)
Q_PROPERTY(QString workspaces READ workspaces NOTIFY workspacesChanged FINAL)
public:
explicit FontTokens(QObject* parent = nullptr);
void bindFont(AppearanceFont* font);
[[nodiscard]] FontStyle* headline() const;
[[nodiscard]] FontStyle* title() const;
[[nodiscard]] FontStyle* body() const;
[[nodiscard]] FontStyle* label() const;
[[nodiscard]] FontStyle* mono() const;
[[nodiscard]] IconFontStyle* icon() const;
[[nodiscard]] FontBuilder clock() const;
[[nodiscard]] QString workspaces() const;
signals:
void clockChanged();
void workspacesChanged();
private:
void rebuildClock();
void rebuildScale();
AppearanceFont* m_font = nullptr;
FontStyle* m_headline;
FontStyle* m_title;
FontStyle* m_body;
FontStyle* m_label;
FontStyle* m_mono;
IconFontStyle* m_icon;
QFont m_clock;
};
} // namespace ZShell::config
+105
View File
@@ -0,0 +1,105 @@
#include "fontbuilder.hpp"
#include <qloggingcategory.h>
namespace {
Q_LOGGING_CATEGORY(lcFontBuilder, "zshell.fontbuilder", QtInfoMsg)
} // namespace
namespace ZShell::config {
FontBuilder::FontBuilder(const QFont& font) : m_font(font) {}
FontBuilder FontBuilder::family(const QString& family) {
m_font.setFamily(family);
return *this;
}
FontBuilder FontBuilder::size(int pointSize) {
if (pointSize <= 0) pointSize = 1;
m_font.setPointSize(pointSize);
m_font.setVariableAxis("opsz", static_cast<float>(pointSize));
return *this;
}
FontBuilder FontBuilder::pixelSize(int pixelSize) {
if (pixelSize <= 0) pixelSize = 1;
m_font.setPixelSize(pixelSize);
return *this;
}
FontBuilder FontBuilder::weight(QFont::Weight weight) {
m_font.setWeight(weight);
m_font.setVariableAxis("wght", static_cast<float>(weight));
return *this;
}
FontBuilder FontBuilder::italic(bool on) {
m_font.setItalic(on);
return *this;
}
FontBuilder FontBuilder::stretch(int stretch) {
m_font.setStretch(stretch);
return *this;
}
FontBuilder FontBuilder::letterSpacing(qreal spacing, bool absolute) {
m_font.setLetterSpacing(
absolute ? QFont::AbsoluteSpacing : QFont::PercentageSpacing, spacing);
return *this;
}
FontBuilder FontBuilder::capitalisation(QFont::Capitalization cap) {
m_font.setCapitalization(cap);
return *this;
}
FontBuilder FontBuilder::vaxis(const QString& tag, float value) {
if (auto t = QFont::Tag::fromString(tag))
m_font.setVariableAxis(*t, value);
else
qCWarning(lcFontBuilder)
<< "Unable to convert tag" << tag << "to QFont::Tag";
return *this;
}
FontBuilder FontBuilder::vaxes(const QVariantMap& axes) {
for (auto it = axes.constBegin(); it != axes.constEnd(); ++it) {
if (it.value().canConvert<float>()) {
if (auto tag = QFont::Tag::fromString(it.key()))
m_font.setVariableAxis(*tag, it.value().toFloat());
else
qCWarning(lcFontBuilder)
<< "Unable to convert tag" << it.key() << "to QFont::Tag";
} else {
qCWarning(lcFontBuilder)
<< "Unable to convert value" << it.value() << "to float";
}
}
return *this;
}
QFont FontBuilder::build() const {
return m_font;
}
FontBuilder FontBuilder::fill(float value) {
return vaxis(QStringLiteral("FILL"), value);
}
FontBuilder FontBuilder::grade(float value) {
return vaxis(QStringLiteral("GRAD"), value);
}
FontBuilder FontBuilder::width(float value) {
return vaxis(QStringLiteral("wdth"), value);
}
FontBuilder FontBuilder::scale(qreal factor) {
return size(static_cast<int>(m_font.pointSize() * factor));
}
} // namespace ZShell::config
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <qfont.h>
#include <qqmlintegration.h>
#include <qvariant.h>
namespace ZShell::config {
class FontBuilder {
Q_GADGET
QML_ANONYMOUS
public:
FontBuilder() = default;
explicit FontBuilder(const QFont& font);
[[nodiscard]] Q_INVOKABLE FontBuilder family(const QString& family);
[[nodiscard]] Q_INVOKABLE FontBuilder size(int pointSize);
[[nodiscard]] Q_INVOKABLE FontBuilder pixelSize(int pixelSize);
[[nodiscard]] Q_INVOKABLE FontBuilder weight(QFont::Weight weight);
[[nodiscard]] Q_INVOKABLE FontBuilder italic(bool on = true);
[[nodiscard]] Q_INVOKABLE FontBuilder stretch(int stretch);
[[nodiscard]] Q_INVOKABLE FontBuilder
letterSpacing(qreal spacing, bool absolute = true);
[[nodiscard]] Q_INVOKABLE FontBuilder
capitalisation(QFont::Capitalization cap);
[[nodiscard]] Q_INVOKABLE FontBuilder vaxis(const QString& tag, float value);
[[nodiscard]] Q_INVOKABLE FontBuilder vaxes(const QVariantMap& axes);
[[nodiscard]] Q_INVOKABLE QFont build() const;
[[nodiscard]] Q_INVOKABLE FontBuilder fill(float value);
[[nodiscard]] Q_INVOKABLE FontBuilder grade(float value);
[[nodiscard]] Q_INVOKABLE FontBuilder width(float value);
[[nodiscard]] Q_INVOKABLE FontBuilder scale(qreal factor);
private:
QFont m_font;
};
} // namespace ZShell::config
+48 -103
View File
@@ -1,158 +1,103 @@
#pragma once
#include "configlist.hpp"
#include "configobject.hpp"
#include "common.hpp"
#include <QStandardPaths>
#include <QStringList>
namespace ZShell::config {
class BatteryThresholdEntry : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class BatteryThresholdEntry : public settings::ObjectNode {
CONFIG_NODE(BatteryThresholdEntry, settings::ObjectNode)
CFG_PROPERTY(QString, icon)
CFG_PROPERTY(QString, message)
CFG_PROPERTY(QString, name)
CFG_PROPERTY(int, perc)
public:
explicit BatteryThresholdEntry(QObject* parent = nullptr)
: ConfigObject(parent) {}
[[nodiscard]] QStringList identityKeys() const override {
return {QStringLiteral("perc")};
}
CONFIG_PROPERTY(QString, icon, {})
CONFIG_PROPERTY(QString, message, {})
CONFIG_PROPERTY(QString, name, {})
CONFIG_PROPERTY(int, perc, {})
};
CONFIG_LIST_TYPE(BatteryThresholdEntry, BatteryThresholdList)
class IdleTimeoutEntry : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class IdleTimeoutEntry : public settings::ObjectNode {
CONFIG_NODE(IdleTimeoutEntry, settings::ObjectNode)
CFG_PROPERTY(QString, idleAction)
CFG_PROPERTY(QString, name)
CFG_PROPERTY(int, timeout)
public:
explicit IdleTimeoutEntry(QObject* parent = nullptr)
: ConfigObject(parent) {}
[[nodiscard]] QStringList identityKeys() const override {
return {QStringLiteral("name")};
}
CONFIG_PROPERTY(QString, idleAction, {})
CONFIG_PROPERTY(QString, name, {})
CONFIG_PROPERTY(int, timeout, {})
};
CONFIG_LIST_TYPE(IdleTimeoutEntry, IdleTimeoutList)
class Apps : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Apps : public settings::ObjectNode {
CONFIG_NODE(Apps, settings::ObjectNode)
CFG_PROPERTY(QStringList, archiver, {QStringLiteral("ark")})
CFG_PROPERTY(QStringList, audio, {QStringLiteral("pavucontrol")})
CFG_PROPERTY(QStringList, document, {QStringLiteral("libreoffice")})
CFG_PROPERTY(QStringList, editor, {QStringLiteral("kate")})
CFG_PROPERTY(QStringList, explorer, {QStringLiteral("dolphin")})
CFG_PROPERTY(QStringList, image, {QStringLiteral("imv")})
CFG_PROPERTY(QStringList, playback, {QStringLiteral("vlc")})
CFG_PROPERTY(QStringList, terminal, {QStringLiteral("wezterm")})
public:
explicit Apps(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(QStringList, archiver, {QStringLiteral("ark")})
CONFIG_PROPERTY(QStringList, audio, {QStringLiteral("pavucontrol")})
CONFIG_PROPERTY(QStringList, document, {QStringLiteral("libreoffice")})
CONFIG_PROPERTY(QStringList, editor, {QStringLiteral("kate")})
CONFIG_PROPERTY(QStringList, explorer, {QStringLiteral("dolphin")})
CONFIG_PROPERTY(QStringList, image, {QStringLiteral("imv")})
CONFIG_PROPERTY(QStringList, playback, {QStringLiteral("vlc")})
CONFIG_PROPERTY(QStringList, terminal, {QStringLiteral("wezterm")})
};
class Battery : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Battery : public settings::ObjectNode {
CONFIG_NODE(Battery, settings::ObjectNode)
CFG_PROPERTY(int, critPerc, 5)
CONFIG_PROPERTY(int, critPerc, 5)
CONFIG_LIST(
BatteryThresholdList,
popupThresholds,
{
vmap(
DEFAULT_ARG({
settings::vmap(
{{"icon", "battery_android_frame_2"},
{"message", "Battery low"},
{"name", "Low battery"},
{"perc", 20}}),
vmap(
settings::vmap(
{{"icon", "battery_android_frame_2"},
{"message", "Battery lower"},
{"name", "Low battery"},
{"perc", 15}}),
vmap(
settings::vmap(
{{"icon", "battery_android_frame_2"},
{"message", "Battery lowest"},
{"name", "Low battery"},
{"perc", 10}}),
})
public:
explicit Battery(QObject* parent = nullptr) : ConfigObject(parent) {}
}))
};
class ColorSettings : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
CFG_PROPERTY(int, hyprsunsetTemp, 2600)
CFG_PROPERTY(QString, mode, QStringLiteral("dark"))
CFG_PROPERTY(bool, neovimColors, false)
CFG_PROPERTY(bool, scheduleDark, false)
CFG_PROPERTY(int, scheduleDarkEnd, 600)
CFG_PROPERTY(int, scheduleDarkStart, 1140)
CFG_PROPERTY(bool, scheduleHyprsunset, true)
CFG_PROPERTY(int, scheduleHyprsunsetEnd, 570)
CFG_PROPERTY(int, scheduleHyprsunsetStart, 1200)
CFG_PROPERTY(bool, schemeGeneration, true)
CFG_PROPERTY(bool, smart, false)
public:
explicit ColorSettings(QObject* parent = nullptr) : ConfigObject(parent) {}
};
class Idle : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Idle : public settings::ObjectNode {
CONFIG_NODE(Idle, settings::ObjectNode)
CONFIG_LIST(
IdleTimeoutList,
timeouts,
{
vmap(
DEFAULT_ARG({
settings::vmap(
{{"idleAction", QStringLiteral("lock")},
{"name", QStringLiteral("Lock")},
{"timeout", 180}}),
})
public:
explicit Idle(QObject* parent = nullptr) : ConfigObject(parent) {}
}))
};
class General : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class General : public settings::ObjectNode {
CONFIG_NODE(General, settings::ObjectNode)
CONFIG_SUBOBJECT(Apps, apps)
CONFIG_SUBOBJECT(Battery, battery)
CONFIG_SUBOBJECT(ColorSettings, color)
CFG_PROPERTY(QString, dateFormat, QStringLiteral("ddd d MMM - hh:mm:ss"))
CFG_PROPERTY(bool, desktopIcons, true)
CONFIG_PROPERTY(QString, dateFormat, QStringLiteral("ddd d MMM - hh:mm:ss"))
CONFIG_PROPERTY(bool, desktopIcons, true)
CONFIG_SUBOBJECT(Idle, idle)
CFG_PROPERTY(QString, logo, QString())
CFG_PROPERTY(bool, showOverFullscreen, true)
CFG_PROPERTY(
CONFIG_PROPERTY(
QString,
lyricsDir,
QStandardPaths::writableLocation(QStandardPaths::MusicLocation) +
QStringLiteral("/Lyrics/"))
CONFIG_PROPERTY(QString, logo, QString())
CONFIG_PROPERTY(bool, showOverFullscreen, true)
CONFIG_PROPERTY(
QString,
wallpaperPath,
QStringLiteral("/mnt/IronWolf/SDImages/SWWW_Wals/"))
public:
explicit General(QObject* parent = nullptr)
: ConfigObject(parent)
, m_apps(new Apps(this))
, m_battery(new Battery(this))
, m_color(new ColorSettings(this))
, m_idle(new Idle(this)) {}
};
} // namespace ZShell::config
+39 -80
View File
@@ -1,68 +1,53 @@
#pragma once
#include "configlist.hpp"
#include "configobject.hpp"
#include "common.hpp"
#include <QStringList>
namespace ZShell::config {
class LauncherActionEntry : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
using Qt::StringLiterals::operator""_s;
using settings::vmap;
CFG_PROPERTY(QVariantList, command)
CFG_PROPERTY(bool, dangerous, false)
CFG_PROPERTY(QString, description)
CFG_PROPERTY(bool, enabled, true)
CFG_PROPERTY(QString, icon)
CFG_PROPERTY(QString, name)
class LauncherActionEntry : public settings::ObjectNode {
CONFIG_NODE(LauncherActionEntry, settings::ObjectNode)
public:
explicit LauncherActionEntry(QObject* parent = nullptr)
: ConfigObject(parent) {}
[[nodiscard]] QStringList identityKeys() const override {
return {QStringLiteral("name")};
}
CONFIG_PROPERTY(QVariantList, command, {})
CONFIG_PROPERTY(bool, dangerous, false)
CONFIG_PROPERTY(QString, description, {})
CONFIG_PROPERTY(bool, enabled, true)
CONFIG_PROPERTY(QString, icon, {})
CONFIG_PROPERTY(QString, name, {})
};
CONFIG_LIST_TYPE(LauncherActionEntry, LauncherActionList)
class LauncherSizes : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class LauncherSizes : public settings::ObjectNode {
CONFIG_NODE(LauncherSizes, settings::ObjectNode)
CFG_PROPERTY(int, itemHeight, 50)
CFG_PROPERTY(int, itemWidth, 600)
CFG_PROPERTY(int, wallpaperHeight, 200)
CFG_PROPERTY(int, wallpaperWidth, 280)
public:
explicit LauncherSizes(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(int, itemHeight, 50)
CONFIG_PROPERTY(int, itemWidth, 600)
CONFIG_PROPERTY(int, wallpaperHeight, 200)
CONFIG_PROPERTY(int, wallpaperWidth, 280)
};
class UseFuzzy : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class UseFuzzy : public settings::ObjectNode {
CONFIG_NODE(UseFuzzy, settings::ObjectNode)
CFG_PROPERTY(bool, actions, true)
CFG_PROPERTY(bool, apps, true)
CFG_PROPERTY(bool, schemes, true)
CFG_PROPERTY(bool, variants, true)
CFG_PROPERTY(bool, wallpapers, true)
public:
explicit UseFuzzy(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(bool, actions, true)
CONFIG_PROPERTY(bool, apps, true)
CONFIG_PROPERTY(bool, schemes, true)
CONFIG_PROPERTY(bool, variants, true)
CONFIG_PROPERTY(bool, wallpapers, true)
};
class Launcher : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Launcher : public settings::ObjectNode {
CONFIG_NODE(Launcher, settings::ObjectNode)
CFG_PROPERTY(QString, actionPrefix, QStringLiteral("<"))
CONFIG_PROPERTY(QString, actionPrefix, QStringLiteral("<"))
CONFIG_LIST(
LauncherActionList,
actions,
{
DEFAULT_ARG({
vmap(
{{"command", QVariantList{"autocomplete", "calc"}},
{"dangerous", false},
@@ -134,44 +119,18 @@ class Launcher : public ConfigObject {
{"enabled", true},
{"icon", "bedtime"},
{"name", "Sleep"}}),
})
CFG_PROPERTY(int, dragThreshold, 150)
CFG_PROPERTY(bool, enableDangerousActions, true)
CFG_PROPERTY(bool, enabled, true)
CFG_PROPERTY(
QStringList,
favoriteApps,
{QStringLiteral("Balatro"),
QStringLiteral("Slay the Spire 2"),
QStringLiteral("com.obsproject.Studio")})
CFG_PROPERTY(
QStringList,
hiddenApps,
{QStringLiteral("zellij"),
QStringLiteral("vim"),
QStringLiteral("jshell-java-openjdk"),
QStringLiteral("jconsole-java-openjdk"),
QStringLiteral("jshell-java21-openjdk"),
QStringLiteral("jconsole-java21-openjdk"),
QStringLiteral("jshell-java17-openjdk"),
QStringLiteral("jconsole-java17-openjdk"),
QStringLiteral("org.neovim.nvim"),
QStringLiteral("avahi-discover"),
QStringLiteral("bvnc"),
QStringLiteral("bssh"),
QStringLiteral("nm-connection-editor")})
CFG_PROPERTY(int, maxAppsShown, 18)
CFG_PROPERTY(int, maxWallpapers, 7)
}))
CONFIG_PROPERTY(int, dragThreshold, 150)
CONFIG_PROPERTY(bool, enableDangerousActions, true)
CONFIG_PROPERTY(bool, enabled, true)
CONFIG_PROPERTY(QStringList, favoriteApps, {})
CONFIG_PROPERTY(QStringList, hiddenApps, {})
CONFIG_PROPERTY(int, maxAppsShown, 18)
CONFIG_PROPERTY(int, maxWallpapers, 7)
CONFIG_SUBOBJECT(LauncherSizes, sizes)
CFG_PROPERTY(QString, specialPrefix, QStringLiteral("@"))
CONFIG_PROPERTY(QString, specialPrefix, QStringLiteral("@"))
CONFIG_SUBOBJECT(UseFuzzy, useFuzzy)
CFG_PROPERTY(bool, uwsm, true)
public:
explicit Launcher(QObject* parent = nullptr)
: ConfigObject(parent)
, m_sizes(new LauncherSizes(this))
, m_useFuzzy(new UseFuzzy(this)) {}
CONFIG_PROPERTY(bool, uwsm, true)
};
} // namespace ZShell::config
+14 -23
View File
@@ -1,36 +1,27 @@
#pragma once
#include "configobject.hpp"
#include "common.hpp"
#include <qqmlintegration.h>
namespace ZShell::config {
class LockSizes : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class LockSizes : public settings::ObjectNode {
CONFIG_NODE(LockSizes, settings::ObjectNode)
CFG_PROPERTY(int, centerWidth, 600)
CFG_PROPERTY(qreal, heightMult, 0.7)
CFG_PROPERTY(qreal, ratio, 1.78)
public:
explicit LockSizes(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(int, centerWidth, 600)
CONFIG_PROPERTY(qreal, heightMult, 0.7)
CONFIG_PROPERTY(qreal, ratio, 1.78)
};
class Lock : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Lock : public settings::ObjectNode {
CONFIG_NODE(Lock, settings::ObjectNode)
CFG_PROPERTY(int, blurAmount, 10)
CFG_PROPERTY(bool, enableFprint, true)
CFG_PROPERTY(int, maxFprintTries, 3)
CFG_PROPERTY(bool, recolorLogo, false)
CFG_PROPERTY(bool, showNotifContent, true)
CFG_PROPERTY(bool, showNotifIcon, true)
CONFIG_PROPERTY(int, blurAmount, 10)
CONFIG_PROPERTY(bool, enableFprint, true)
CONFIG_PROPERTY(int, maxFprintTries, 3)
CONFIG_PROPERTY(bool, recolorLogo, false)
CONFIG_PROPERTY(bool, showNotifContent, true)
CONFIG_PROPERTY(bool, showNotifIcon, true)
CONFIG_SUBOBJECT(LockSizes, sizes)
public:
explicit Lock(QObject* parent = nullptr)
: ConfigObject(parent), m_sizes(new LockSizes(this)) {}
};
} // namespace ZShell::config
+123
View File
@@ -0,0 +1,123 @@
#include "migration.hpp"
#include <QDebug>
namespace ZShell::config {
namespace {
// A stored value is "missing" if the path does not resolve to an entry.
// Note: default-constructed QJsonValue is Null, not Undefined.
bool isMissing(const QJsonValue& v) {
return v.isUndefined() || v.isNull();
}
QJsonValue valueAt(const QJsonObject& obj, const QStringList& path) {
QJsonObject cur = obj;
for (int i = 0; i < path.size() - 1; ++i) {
if (!cur.value(path.at(i)).isObject()) return QJsonValue::Undefined;
cur = cur.value(path.at(i)).toObject();
}
const QString key = path.last();
if (!cur.contains(key)) return QJsonValue::Undefined;
return cur.value(key);
}
QJsonObject placeAt(QJsonObject obj, QStringList path, const QJsonValue& value) {
const QString head = path.takeFirst();
if (path.isEmpty()) {
obj[head] = value;
return obj;
}
QJsonObject child = obj.value(head).isObject() ? obj.value(head).toObject()
: QJsonObject();
obj[head] = placeAt(child, path, value);
return obj;
}
// Removes `path` from `obj`. Returns true if a value was removed.
// Parent objects left empty are removed as well.
bool removeAt(QJsonObject& obj, const QStringList& path) {
const QString head = path.first();
if (path.size() == 1) {
if (!obj.contains(head)) return false;
obj.remove(head);
return true;
}
if (!obj.value(head).isObject()) return false;
QJsonObject child = obj.value(head).toObject();
const bool removed = removeAt(child, path.mid(1));
if (child.isEmpty())
obj.remove(head);
else
obj[head] = child;
return removed;
}
} // namespace
const QList<ConfigMigrationRule>& ConfigMigrations::rules() {
static const QList<ConfigMigrationRule> s_rules = {
migrate(
"general.color.scheduleHyprsunset", "display.nightlight.schedule"),
migrate(
"general.color.scheduleHyprsunsetStart",
"display.nightlight.scheduleStart"),
migrate(
"general.color.scheduleHyprsunsetEnd",
"display.nightlight.scheduleEnd"),
migrate("general.color.scheduleDark", "colors.scheduleDark"),
migrate("general.color.scheduleDarkStart", "colors.scheduleDarkStart"),
migrate("general.color.scheduleDarkEnd", "colors.scheduleDarkEnd"),
migrate("general.color.schemeGeneration", "colors.schemeGen"),
migrate(
"general.color.useNativeNightlight",
"display.nightlight.useNative"),
migrate(
"general.color.nativeFadeDuration",
"display.nightlight.fadeDuration"),
migrate("general.color.hyprsunsetTemp", "display.nightlight.temp"),
migrate("general.color.neovimColors", "colors.neovimColors"),
migrate("general.color.mode", "colors.mode"),
migrate("general.color.smart", "colors.smart"),
};
return s_rules;
}
QJsonObject ConfigMigrations::apply(const QJsonObject& json) {
QJsonObject obj = json;
for (const auto& rule : rules()) {
const QStringList from =
rule.from.split(QLatin1Char('.'), Qt::SkipEmptyParts);
const QStringList to =
rule.to.split(QLatin1Char('.'), Qt::SkipEmptyParts);
if (from.isEmpty() || to.isEmpty()) continue;
const QJsonValue oldValue = valueAt(obj, from);
if (isMissing(oldValue)) continue;
const bool targetExists = !isMissing(valueAt(obj, to));
if (!targetExists) obj = placeAt(obj, to, oldValue);
if (removeAt(obj, from)) {
qInfo() << "Config migration:" << rule.from << "->" << rule.to
<< (targetExists ? "(target already set, old value dropped)"
: "(value moved)");
}
}
return obj;
}
} // namespace ZShell::config
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include <QJsonObject>
#include <QStringList>
namespace ZShell::config {
// A single migration rule: move the value stored at the dotted `from` path
// to the dotted `to` path, then delete the old key (and any parent objects
// left empty).
//
// Rules are applied to the raw JSON file on every load, before the schema
// loads it. They are idempotent: once the old key is gone the rule never
// fires again, so no version stamping is needed. Old rules can stay in the
// table forever.
//
// WARNING: the target path must exist in the schema (i.e. a ConfigObject
// property somewhere in the tree). If it does not, the migrated value is
// loaded as an unknown key and silently dropped on the next save.
struct ConfigMigrationRule {
QString from;
QString to;
};
// Helper for writing rules: migrate("general.color.foo", "display.bar.foo")
inline ConfigMigrationRule migrate(const QString& from, const QString& to) {
return {from, to};
}
class ConfigMigrations {
public:
[[nodiscard]] static const QList<ConfigMigrationRule>& rules();
// Applies all rules to `json`, returning the migrated object.
[[nodiscard]] static QJsonObject apply(const QJsonObject& json);
};
} // namespace ZShell::config
+17 -26
View File
@@ -1,39 +1,30 @@
#pragma once
#include "configobject.hpp"
#include "common.hpp"
#include <qqmlintegration.h>
namespace ZShell::config {
class NotifsSizes : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class NotifsSizes : public settings::ObjectNode {
CONFIG_NODE(NotifsSizes, settings::ObjectNode)
CFG_PROPERTY(int, badge, 20)
CFG_PROPERTY(int, image, 41)
CFG_PROPERTY(int, width, 400)
public:
explicit NotifsSizes(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(int, badge, 20)
CONFIG_PROPERTY(int, image, 41)
CONFIG_PROPERTY(int, width, 400)
};
class Notifs : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Notifs : public settings::ObjectNode {
CONFIG_NODE(Notifs, settings::ObjectNode)
CFG_PROPERTY(bool, actionOnClick, false)
CFG_PROPERTY(int, appNotifCooldown, 0)
CFG_PROPERTY(qreal, clearThreshold, 0.3)
CFG_PROPERTY(int, defaultExpireTimeout, 5000)
CFG_PROPERTY(int, expandThreshold, 20)
CFG_PROPERTY(bool, expire, true)
CFG_PROPERTY(int, groupPreviewNum, 5)
CFG_PROPERTY(bool, openExpanded, false)
CFG_PROPERTY(bool, showInFullscreen, false)
CONFIG_PROPERTY(bool, actionOnClick, false)
CONFIG_PROPERTY(int, appNotifCooldown, 0)
CONFIG_PROPERTY(qreal, clearThreshold, 0.3)
CONFIG_PROPERTY(int, defaultExpireTimeout, 5000)
CONFIG_PROPERTY(int, expandThreshold, 20)
CONFIG_PROPERTY(bool, expire, true)
CONFIG_PROPERTY(int, groupPreviewNum, 5)
CONFIG_PROPERTY(bool, openExpanded, false)
CONFIG_PROPERTY(bool, showInFullscreen, false)
CONFIG_SUBOBJECT(NotifsSizes, sizes)
public:
explicit Notifs(QObject* parent = nullptr)
: ConfigObject(parent), m_sizes(new NotifsSizes(this)) {}
};
} // namespace ZShell::config
+12 -21
View File
@@ -1,34 +1,25 @@
#pragma once
#include "configobject.hpp"
#include "common.hpp"
#include <qqmlintegration.h>
namespace ZShell::config {
class OsdSizes : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class OsdSizes : public settings::ObjectNode {
CONFIG_NODE(OsdSizes, settings::ObjectNode)
CFG_PROPERTY(int, sliderHeight, 150)
CFG_PROPERTY(int, sliderWidth, 30)
public:
explicit OsdSizes(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(int, sliderHeight, 150)
CONFIG_PROPERTY(int, sliderWidth, 30)
};
class Osd : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Osd : public settings::ObjectNode {
CONFIG_NODE(Osd, settings::ObjectNode)
CFG_PROPERTY(bool, allMonBrightness, true)
CFG_PROPERTY(bool, enableBrightness, true)
CFG_PROPERTY(bool, enableMicrophone, true)
CFG_PROPERTY(bool, enabled, true)
CFG_PROPERTY(int, hideDelay, 3000)
CONFIG_PROPERTY(bool, allMonBrightness, true)
CONFIG_PROPERTY(bool, enableBrightness, true)
CONFIG_PROPERTY(bool, enableMicrophone, true)
CONFIG_PROPERTY(bool, enabled, true)
CONFIG_PROPERTY(int, hideDelay, 3000)
CONFIG_SUBOBJECT(OsdSizes, sizes)
public:
explicit Osd(QObject* parent = nullptr)
: ConfigObject(parent), m_sizes(new OsdSizes(this)) {}
};
} // namespace ZShell::config
+191
View File
@@ -0,0 +1,191 @@
#include "rootnodes.hpp"
#include <qjsondocument.h>
#include <qsavefile.h>
#include "../toaster.hpp"
#include "common.hpp"
#include "migration.hpp"
namespace ZShell::config {
using Qt::StringLiterals::operator""_s;
namespace {
QString nameFor(const QString& key) {
return key.isEmpty() ? u"global"_s : key;
}
} // namespace
namespace detail {
void loaded(ConfigKind kind, settings::RootNode* layer, const QString& screen) {
if (kind != ConfigKind::Shell || !screen.isEmpty()) return;
auto* const config = static_cast<ConfigRoot*>(layer);
if (!config->utilities()->toasts()->configLoaded()) return;
const auto issues = static_cast<int>(config->diagnostics().count());
const auto message = issues > 0
? QStringLiteral("Config loaded with %1 issue%2")
.arg(issues)
.arg(issues == 1 ? "" : "s")
: QStringLiteral("Config loaded successfully!");
Toaster::instance()->toast(
QStringLiteral("Config loaded"),
message,
issues > 0 ? QStringLiteral("settings_alert")
: QStringLiteral("rule_settings"),
issues > 0 ? Toast::Type::Warning : Toast::Type::Info);
}
void loadFailed(ConfigKind kind, const QString& error, const QString& screen) {
QString title;
if (kind == ConfigKind::Tokens)
title = screen.isEmpty()
? QStringLiteral("Failed to parse token config")
: QStringLiteral("Failed to parse token config for %1")
.arg(screen);
else
title =
screen.isEmpty()
? QStringLiteral("Failed to parse config")
: QStringLiteral("Failed to parse config for %1").arg(screen);
Toaster::instance()->toast(
title, error, QStringLiteral("settings_alert"), Toast::Type::Warning);
}
void saveFailed(ConfigKind kind, const QString& error, const QString& screen) {
if (kind != ConfigKind::Shell) return;
const auto title =
screen.isEmpty()
? QStringLiteral("Failed to save config")
: QStringLiteral("Failed to save config for %1").arg(screen);
Toaster::instance()->toast(
title, error, QStringLiteral("settings_alert"), Toast::Type::Error);
}
} // namespace detail
ConfigRoot::ConfigRoot(
const QString& path, ConfigRoot* fallback, QObject* parent)
: RootNode(path, fallback, parent) {
migrateFile(path);
bindTokens();
qCDebug(lcConfig) << "Created config root for" << nameFor(key());
}
void ConfigRoot::migrateFile(const QString& path) {
QFile file(path);
if (!file.exists() || !file.open(QIODevice::ReadOnly)) return;
const auto doc = QJsonDocument::fromJson(file.readAll());
if (!doc.isObject()) return;
const QJsonObject raw = doc.object();
const QJsonObject migrated = ConfigMigrations::apply(raw);
if (migrated == raw) return;
file.close();
QSaveFile save(path);
if (!save.open(QIODevice::WriteOnly)) {
qCWarning(lcConfig) << "Failed to open" << path
<< "for migration write:" << save.errorString();
return;
}
if (save.write(QJsonDocument(migrated).toJson(QJsonDocument::Indented)) !=
save.size()) {
qCWarning(lcConfig)
<< "Failed to write migrated config:" << save.errorString();
return;
}
if (!save.commit()) {
qCWarning(lcConfig)
<< "Failed to commit migrated config:" << save.errorString();
return;
}
qCDebug(lcConfig) << "Migrated config file" << path;
}
void ConfigRoot::bindTokens() {
qCDebug(lcConfig) << "Binding appearance to token values for"
<< nameFor(key());
auto* const tokens = TokensSingleton::instance()->appearance();
m_appearance->rounding()->bindTokens(tokens->rounding());
m_appearance->spacing()->bindTokens(tokens->spacing());
m_appearance->padding()->bindTokens(tokens->padding());
m_appearance->anim()->durations()->bindTokens(tokens->animDurations());
}
TokensRoot::TokensRoot(
const QString& path, TokensRoot* fallback, QObject* parent)
: RootNode(path, fallback, parent) {
qCDebug(lcConfig) << "Created tokens root for" << nameFor(key());
}
#define SINGLETON_IMPL(Type, Root, file, kind) \
Type* Type::instance() { \
static Type instance; \
return &instance; \
} \
\
Type* Type::create(QQmlEngine*, QJSEngine*) { \
QQmlEngine::setObjectOwnership(instance(), QQmlEngine::CppOwnership); \
return instance(); \
} \
\
Root* Type::forScreen(const QString& screen) { \
bool created; \
auto* const layer = m_layers.get(screen, this, &created); \
if (created) initLayer(layer); \
return layer; \
} \
\
Type::Type(QObject* parent) \
: Root(configDir() + u'/' + file, nullptr, parent) \
, m_layers(monitorConfigDir(), file, this) { \
initLayer(this); \
} \
\
void Type::onTreeLoaded(settings::RootNode* layer) { \
detail::loaded( \
kind, layer, m_layers.nameFor(static_cast<Root*>(layer))); \
} \
\
void Type::onTreeLoadFailed( \
settings::RootNode* layer, const QString& error) { \
detail::loadFailed( \
kind, error, m_layers.nameFor(static_cast<Root*>(layer))); \
} \
\
void Type::onTreeSaveFailed( \
settings::RootNode* layer, const QString& error) { \
detail::saveFailed( \
kind, error, m_layers.nameFor(static_cast<Root*>(layer))); \
} \
\
void Type::initLayer(Root* layer) { \
QObject::connect(layer, &Root::treeLoaded, this, &Type::onTreeLoaded); \
QObject::connect( \
layer, &Root::treeLoadFailed, this, &Type::onTreeLoadFailed); \
QObject::connect( \
layer, &Root::treeSaveFailed, this, &Type::onTreeSaveFailed); \
layer->load(); \
}
SINGLETON_IMPL(
ConfigSingleton, ConfigRoot, u"config.json"_s, detail::ConfigKind::Shell)
SINGLETON_IMPL(
TokensSingleton, TokensRoot, u"tokens.json"_s, detail::ConfigKind::Tokens)
#undef SINGLETON_IMPL
} // namespace ZShell::config
+168
View File
@@ -0,0 +1,168 @@
#pragma once
#include <qjsengine.h>
#include <qqmlengine.h>
#include "settings/layerregistry.hpp"
#include "settings/rootnode.hpp"
#include "appearance.hpp"
#include "background.hpp"
#include "bar.hpp"
#include "clipboard.hpp"
#include "colors.hpp"
#include "common.hpp"
#include "dashboard.hpp"
#include "display.hpp"
#include "dock.hpp"
#include "general.hpp"
#include "launcher.hpp"
#include "llm.hpp"
#include "lock.hpp"
#include "notifs.hpp"
#include "osd.hpp"
#include "screenshot.hpp"
#include "services.hpp"
#include "sidebar.hpp"
#include "tokens.hpp"
#include "utilities.hpp"
namespace ZShell::config {
class ConfigRoot : public settings::RootNode {
CONFIG_NODE_NO_CTOR(ConfigRoot, settings::RootNode)
QML_ANONYMOUS
CONFIG_PROPERTY(bool, enabled, true)
CONFIG_SUBOBJECT(Appearance, appearance)
CONFIG_SUBOBJECT(Background, background)
CONFIG_SUBOBJECT(Bar, bar)
CONFIG_SUBOBJECT(Clipboard, clipboard)
CONFIG_SUBOBJECT(Colors, colors)
CONFIG_SUBOBJECT(Dashboard, dashboard)
CONFIG_SUBOBJECT(Display, display)
CONFIG_SUBOBJECT(Dock, dock)
CONFIG_SUBOBJECT(General, general)
CONFIG_SUBOBJECT(Launcher, launcher)
CONFIG_SUBOBJECT(Llm, llm)
CONFIG_SUBOBJECT(Lock, lock)
CONFIG_SUBOBJECT(Notifs, notifs)
CONFIG_SUBOBJECT(Osd, osd)
CONFIG_SUBOBJECT(Screenshot, screenshot)
CONFIG_SUBOBJECT(Services, services)
CONFIG_SUBOBJECT(Sidebar, sidebar)
CONFIG_SUBOBJECT(Utilities, utilities)
public:
explicit ConfigRoot(
const QString& path,
ConfigRoot* fallback = nullptr,
QObject* parent = nullptr);
private:
// Binds the computed appearance values to the global token base values
void bindTokens();
// Applies ConfigMigrations to the file before the first load
void migrateFile(const QString& path);
};
class TokensRoot : public settings::RootNode {
CONFIG_NODE_NO_CTOR(TokensRoot, settings::RootNode)
QML_ANONYMOUS
CONFIG_SUBOBJECT(AppearanceTokens, appearance)
CONFIG_SUBOBJECT(SizeTokens, sizes)
public:
explicit TokensRoot(
const QString& path,
TokensRoot* fallback = nullptr,
QObject* parent = nullptr);
};
namespace detail {
enum class ConfigKind : quint8 { Shell, Tokens };
void loaded(ConfigKind kind, settings::RootNode* layer, const QString& screen);
void loadFailed(ConfigKind kind, const QString& error, const QString& screen);
void saveFailed(ConfigKind kind, const QString& error, const QString& screen);
} // namespace detail
#define SINGLETON_DECL(Type, Root, QmlName) \
class Type : public Root { \
Q_OBJECT \
QML_NAMED_ELEMENT(QmlName) \
QML_SINGLETON \
\
public: \
static Type* instance(); \
static Type* create(QQmlEngine*, QJSEngine*); \
\
[[nodiscard]] Q_INVOKABLE Root* forScreen(const QString& screen); \
\
private: \
explicit Type(QObject* parent = nullptr); \
\
void onTreeLoaded(settings::RootNode* layer); \
void onTreeLoadFailed(settings::RootNode* layer, const QString& error); \
void onTreeSaveFailed(settings::RootNode* layer, const QString& error); \
\
void initLayer(Root* layer); \
\
settings::LayerRegistry<Root> m_layers; \
};
SINGLETON_DECL(ConfigSingleton, ConfigRoot, Config)
SINGLETON_DECL(TokensSingleton, TokensRoot, TokenConfig)
#undef SINGLETON_DECL
class Tokens : public QObject {
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
Q_PROPERTY(
ZShell::config::AppearanceRounding* rounding READ rounding CONSTANT)
Q_PROPERTY(ZShell::config::AppearanceSpacing* spacing READ spacing CONSTANT)
Q_PROPERTY(ZShell::config::AppearancePadding* padding READ padding CONSTANT)
Q_PROPERTY(ZShell::config::SizeTokens* sizes READ sizes CONSTANT)
Q_PROPERTY(ZShell::config::FontTokens* font READ font CONSTANT)
Q_PROPERTY(ZShell::config::AnimTokens* anim READ anim CONSTANT)
public:
explicit Tokens(QObject* parent = nullptr)
: QObject(parent)
, m_font(new FontTokens(this))
, m_anim(new AnimTokens(this)) {
m_anim->bindCurves(TokensSingleton::instance()->appearance()->curves());
m_anim->bindDurations(
ConfigSingleton::instance()->appearance()->anim()->durations());
m_font->bindFont(ConfigSingleton::instance()->appearance()->font());
}
[[nodiscard]] AppearanceRounding* rounding() const {
return ConfigSingleton::instance()->appearance()->rounding();
}
[[nodiscard]] AppearancePadding* padding() const {
return ConfigSingleton::instance()->appearance()->padding();
}
[[nodiscard]] AppearanceSpacing* spacing() const {
return ConfigSingleton::instance()->appearance()->spacing();
}
[[nodiscard]] SizeTokens* sizes() const {
return TokensSingleton::instance()->sizes();
}
[[nodiscard]] FontTokens* font() const { return m_font; }
[[nodiscard]] AnimTokens* anim() const { return m_anim; }
static Tokens* create(QQmlEngine*, QJSEngine*) { return new Tokens(); }
private:
FontTokens* m_font;
AnimTokens* m_anim;
};
} // namespace ZShell::config
+13 -17
View File
@@ -1,27 +1,23 @@
#pragma once
#include "configobject.hpp"
#include "common.hpp"
#include <QVariantList>
#include <qqmlintegration.h>
namespace ZShell::config {
class Screenshot : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Screenshot : public settings::ObjectNode {
CONFIG_NODE(Screenshot, settings::ObjectNode)
CFG_PROPERTY(bool, enable_pp, false)
CFG_PROPERTY(QString, mode, QStringLiteral("manual"))
CFG_PROPERTY(int, radius, 49)
CFG_PROPERTY(bool, rounding, false)
CFG_PROPERTY(bool, saveOnCopy, false)
CFG_PROPERTY(bool, shadow, true)
CFG_PROPERTY(int, shadow_blur, 0)
CFG_PROPERTY(QVariantList, shadow_color, (QVariantList{0, 0, 0, 160}))
CFG_PROPERTY(int, shadow_offset_x, 30)
CFG_PROPERTY(int, shadow_offset_y, 50)
public:
explicit Screenshot(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(bool, enable_pp, false)
CONFIG_PROPERTY(QString, mode, QStringLiteral("manual"))
CONFIG_PROPERTY(int, radius, 49)
CONFIG_PROPERTY(bool, rounding, false)
CONFIG_PROPERTY(bool, saveOnCopy, false)
CONFIG_PROPERTY(bool, shadow, true)
CONFIG_PROPERTY(int, shadow_blur, 0)
CONFIG_PROPERTY(QVariantList, shadow_color, (QVariantList{0, 0, 0, 160}))
CONFIG_PROPERTY(int, shadow_offset_x, 30)
CONFIG_PROPERTY(int, shadow_offset_y, 50)
};
} // namespace ZShell::config
+25 -36
View File
@@ -1,54 +1,43 @@
#pragma once
#include "configlist.hpp"
#include "configobject.hpp"
#include "common.hpp"
#include "enums.hpp"
namespace ZShell::config {
class PlayerAliasEntry : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class PlayerAliasEntry : public settings::ObjectNode {
CONFIG_NODE(PlayerAliasEntry, settings::ObjectNode)
CFG_PROPERTY(QString, from)
CFG_PROPERTY(QString, to)
public:
explicit PlayerAliasEntry(QObject* parent = nullptr)
: ConfigObject(parent) {}
[[nodiscard]] QStringList identityKeys() const override {
return {QStringLiteral("from")};
}
CONFIG_PROPERTY(QString, from, {})
CONFIG_PROPERTY(QString, to, {})
};
CONFIG_LIST_TYPE(PlayerAliasEntry, PlayerAliasList)
class Services : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Services : public settings::ObjectNode {
CONFIG_NODE(Services, settings::ObjectNode)
CFG_PROPERTY(qreal, audioIncrement, 0.1)
CFG_PROPERTY(qreal, brightnessIncrement, 0.1)
CFG_PROPERTY(bool, ddcutilService, true)
CFG_PROPERTY(QString, defaultPlayer, QStringLiteral("Spotify"))
CFG_PROPERTY(QString, gpuType, QStringLiteral("NVIDIA"))
CFG_PROPERTY(qreal, maxVolume, 1)
CFG_PROPERTY(qreal, minBrightness, 0.01)
CONFIG_PROPERTY(qreal, audioIncrement, 0.1)
CONFIG_PROPERTY(qreal, brightnessIncrement, 0.1)
CONFIG_PROPERTY(bool, ddcutilService, true)
CONFIG_PROPERTY(QString, defaultPlayer, QStringLiteral("Spotify"))
CONFIG_ENUM_PROPERTY(GpuType, gpuType, GpuType::Auto)
CONFIG_PROPERTY(LyricsBackend::Enum, lyricsBackend, LyricsBackend::Auto)
CONFIG_PROPERTY(qreal, maxVolume, 1)
CONFIG_PROPERTY(qreal, minBrightness, 0.01)
CONFIG_LIST(
PlayerAliasList,
playerAliases,
{
vmap(
DEFAULT_ARG({
settings::vmap(
{{"from", QStringLiteral("com.github.th_ch.youtube_music")},
{"to", QStringLiteral("YT Music")}}),
})
CFG_PROPERTY(bool, updates, true)
CFG_PROPERTY(bool, useFahrenheit, false)
CFG_PROPERTY(bool, useTwelveHourClock, false)
CFG_PROPERTY(int, visualizerBars, 50)
CFG_PROPERTY(QString, weatherLocation, QString())
public:
explicit Services(QObject* parent = nullptr) : ConfigObject(parent) {}
}))
CONFIG_PROPERTY(bool, updates, true)
CONFIG_PROPERTY(bool, useFahrenheit, false)
CONFIG_PROPERTY(bool, useTwelveHourClock, false)
CONFIG_PROPERTY(int, visualizerBars, 50)
CONFIG_PROPERTY(QString, weatherLocation, QString())
};
} // namespace ZShell::config
+8 -17
View File
@@ -1,30 +1,21 @@
#pragma once
#include "configobject.hpp"
#include "common.hpp"
#include <qqmlintegration.h>
namespace ZShell::config {
class SidebarSizes : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class SidebarSizes : public settings::ObjectNode {
CONFIG_NODE(SidebarSizes, settings::ObjectNode)
CFG_PROPERTY(int, width, 520)
public:
explicit SidebarSizes(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(int, width, 520)
};
class Sidebar : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Sidebar : public settings::ObjectNode {
CONFIG_NODE(Sidebar, settings::ObjectNode)
CFG_PROPERTY(int, dragThreshold, 30)
CFG_PROPERTY(bool, enabled, true)
CONFIG_PROPERTY(int, dragThreshold, 30)
CONFIG_PROPERTY(bool, enabled, true)
CONFIG_SUBOBJECT(SidebarSizes, sizes)
public:
explicit Sidebar(QObject* parent = nullptr)
: ConfigObject(parent), m_sizes(new SidebarSizes(this)) {}
};
} // namespace ZShell::config
+136 -254
View File
@@ -1,292 +1,174 @@
#pragma once
#include "configobject.hpp"
#include "anim.hpp"
#include <QVariantList>
#include <limits>
#include <qlist.h>
#include <qobject.h>
#include <qqmlengine.h>
#include <qqmlintegration.h>
#include <qqmlregistration.h>
#include <qtypes.h>
#include <limits>
#include "settings/objectnode.hpp"
#include "anim.hpp"
#include "common.hpp"
#include "font.hpp"
namespace ZShell::config {
class AppearanceRounding : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class AnimCurves : public settings::ObjectNode {
CONFIG_NODE(AnimCurves, settings::ObjectNode)
CFG_PROPERTY(qreal, extraSmall, 4)
CFG_PROPERTY(qreal, full, std::numeric_limits<int>::max())
CFG_PROPERTY(qreal, large, 16)
CFG_PROPERTY(qreal, largeIncreased, 20)
CFG_PROPERTY(qreal, extraLarge, 28)
CFG_PROPERTY(qreal, extraLargeIncreased, 32)
CFG_PROPERTY(qreal, extraExtraLarge, 48)
CFG_PROPERTY(qreal, medium, 12)
CFG_PROPERTY(qreal, scale, 1)
CFG_PROPERTY(qreal, small, 8)
public:
explicit AppearanceRounding(QObject* parent = nullptr)
: ConfigObject(parent) {}
CONFIG_GLOBAL_PROPERTY(
QList<qreal>,
emphasized,
DEFAULT_ARG(
{0.05,
0,
2.0 / 15.0,
0.06,
1.0 / 6.0,
0.4,
5.0 / 24.0,
0.82,
0.25,
1,
1,
1}))
CONFIG_GLOBAL_PROPERTY(
QList<qreal>, emphasizedAccel, DEFAULT_ARG({0.3, 0, 0.8, 0.15, 1, 1}))
CONFIG_GLOBAL_PROPERTY(
QList<qreal>, emphasizedDecel, DEFAULT_ARG({0.05, 0.7, 0.1, 1, 1, 1}))
CONFIG_GLOBAL_PROPERTY(
QList<qreal>, standard, DEFAULT_ARG({0.2, 0, 0, 1, 1, 1}))
CONFIG_GLOBAL_PROPERTY(
QList<qreal>, standardAccel, DEFAULT_ARG({0.3, 0, 1, 1, 1, 1}))
CONFIG_GLOBAL_PROPERTY(
QList<qreal>, standardDecel, DEFAULT_ARG({0, 0, 0, 1, 1, 1}))
CONFIG_GLOBAL_PROPERTY(
QList<qreal>,
expressiveFastSpatial,
DEFAULT_ARG({0.42, 1.67, 0.21, 0.9, 1, 1}))
CONFIG_GLOBAL_PROPERTY(
QList<qreal>,
expressiveDefaultSpatial,
DEFAULT_ARG({0.38, 1.21, 0.22, 1, 1, 1}))
CONFIG_GLOBAL_PROPERTY(
QList<qreal>,
expressiveSlowSpatial,
DEFAULT_ARG({0.39, 1.29, 0.35, 0.98, 1, 1}))
CONFIG_GLOBAL_PROPERTY(
QList<qreal>,
expressiveFastEffects,
DEFAULT_ARG({0.31, 0.94, 0.34, 1, 1, 1}))
CONFIG_GLOBAL_PROPERTY(
QList<qreal>,
expressiveDefaultEffects,
DEFAULT_ARG({0.34, 0.8, 0.34, 1, 1, 1}))
CONFIG_GLOBAL_PROPERTY(
QList<qreal>,
expressiveSlowEffects,
DEFAULT_ARG({0.34, 0.88, 0.34, 1, 1, 1}))
};
class AppearancePadding : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class RoundingTokens : public settings::ObjectNode {
CONFIG_NODE(RoundingTokens, settings::ObjectNode)
CFG_PROPERTY(int, extraSmall, 4)
CFG_PROPERTY(int, smaller, 5)
CFG_PROPERTY(int, small, 8)
CFG_PROPERTY(int, medium, 12)
CFG_PROPERTY(int, large, 16)
CFG_PROPERTY(int, largeIncreased, 20)
CFG_PROPERTY(int, extraLarge, 28)
CFG_PROPERTY(int, extraLargeIncreased, 32)
CFG_PROPERTY(int, extraExtraLarge, 48)
public:
explicit AppearancePadding(QObject* parent = nullptr)
: ConfigObject(parent) {}
CONFIG_PROPERTY(int, extraSmall, 4)
CONFIG_PROPERTY(int, small, 8)
CONFIG_PROPERTY(int, medium, 12)
CONFIG_PROPERTY(int, large, 16)
CONFIG_PROPERTY(int, largeIncreased, 20)
CONFIG_PROPERTY(int, extraLarge, 28)
CONFIG_PROPERTY(int, extraLargeIncreased, 32)
CONFIG_PROPERTY(int, extraExtraLarge, 48)
CONFIG_PROPERTY(int, full, std::numeric_limits<int>::max())
};
class AppearanceSpacing : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class SpacingTokens : public settings::ObjectNode {
CONFIG_NODE(SpacingTokens, settings::ObjectNode)
CFG_PROPERTY(int, extraSmall, 4)
CFG_PROPERTY(int, small, 8)
CFG_PROPERTY(int, medium, 12)
CFG_PROPERTY(int, large, 16)
CFG_PROPERTY(int, largeIncreased, 20)
CFG_PROPERTY(int, extraLarge, 28)
CFG_PROPERTY(int, extraLargeIncreased, 32)
CFG_PROPERTY(int, extraExtraLarge, 48)
public:
explicit AppearanceSpacing(QObject* parent = nullptr)
: ConfigObject(parent) {}
CONFIG_PROPERTY(int, extraSmall, 4)
CONFIG_PROPERTY(int, small, 8)
CONFIG_PROPERTY(int, medium, 12)
CONFIG_PROPERTY(int, large, 16)
CONFIG_PROPERTY(int, largeIncreased, 20)
CONFIG_PROPERTY(int, extraLarge, 28)
CONFIG_PROPERTY(int, extraLargeIncreased, 32)
CONFIG_PROPERTY(int, extraExtraLarge, 48)
};
class FontSizeTokens : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class PaddingTokens : public settings::ObjectNode {
CONFIG_NODE(PaddingTokens, settings::ObjectNode)
CFG_PROPERTY(int, small, 11)
CFG_PROPERTY(int, smaller, 12)
CFG_PROPERTY(int, normal, 13)
CFG_PROPERTY(int, larger, 15)
CFG_PROPERTY(int, large, 18)
CFG_PROPERTY(int, extraLarge, 28)
public:
explicit FontSizeTokens(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(int, extraSmall, 4)
CONFIG_PROPERTY(int, smaller, 5)
CONFIG_PROPERTY(int, small, 8)
CONFIG_PROPERTY(int, medium, 12)
CONFIG_PROPERTY(int, large, 16)
CONFIG_PROPERTY(int, largeIncreased, 20)
CONFIG_PROPERTY(int, extraLarge, 28)
CONFIG_PROPERTY(int, extraLargeIncreased, 32)
CONFIG_PROPERTY(int, extraExtraLarge, 48)
};
class FontTokens : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class AnimDurationTokens : public settings::ObjectNode {
CONFIG_NODE(AnimDurationTokens, settings::ObjectNode)
CONFIG_SUBOBJECT(FontSizeTokens, size)
public:
explicit FontTokens(QObject* parent = nullptr)
: ConfigObject(parent), m_size(new FontSizeTokens(this)) {}
CONFIG_GLOBAL_PROPERTY(int, small, 200)
CONFIG_GLOBAL_PROPERTY(int, normal, 400)
CONFIG_GLOBAL_PROPERTY(int, large, 600)
CONFIG_GLOBAL_PROPERTY(int, extraLarge, 1000)
CONFIG_GLOBAL_PROPERTY(int, expressiveFastSpatial, 350)
CONFIG_GLOBAL_PROPERTY(int, expressiveDefaultSpatial, 500)
CONFIG_GLOBAL_PROPERTY(int, expressiveSlowSpatial, 650)
CONFIG_GLOBAL_PROPERTY(int, expressiveFastEffects, 150)
CONFIG_GLOBAL_PROPERTY(int, expressiveDefaultEffects, 200)
CONFIG_GLOBAL_PROPERTY(int, expressiveSlowEffects, 300)
};
class AnimCurves : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class AppearanceTokens : public settings::ObjectNode {
CONFIG_NODE(AppearanceTokens, settings::ObjectNode)
CFG_PROPERTY(QList<qreal>, emphasized)
CFG_PROPERTY(QList<qreal>, emphasizedAccel)
CFG_PROPERTY(QList<qreal>, emphasizedDecel)
CFG_PROPERTY(QList<qreal>, standard)
CFG_PROPERTY(QList<qreal>, standardAccel)
CFG_PROPERTY(QList<qreal>, standardDecel)
CFG_PROPERTY(QList<qreal>, expressiveFastSpatial)
CFG_PROPERTY(QList<qreal>, expressiveDefaultSpatial)
CFG_PROPERTY(QList<qreal>, expressiveSlowSpatial)
CFG_PROPERTY(QList<qreal>, expressiveFastEffects)
CFG_PROPERTY(QList<qreal>, expressiveDefaultEffects)
CFG_PROPERTY(QList<qreal>, expressiveSlowEffects)
public:
explicit AnimCurves(QObject* parent = nullptr)
: ConfigObject(parent)
, m_emphasized(
{0.05,
0,
2.0 / 15.0,
0.06,
1.0 / 6.0,
0.4,
5.0 / 24.0,
0.82,
0.25,
1,
1,
1})
, m_emphasizedAccel({0.3, 0, 0.8, 0.15, 1, 1})
, m_emphasizedDecel({0.05, 0.7, 0.1, 1, 1, 1})
, m_standard({0.2, 0, 0, 1, 1, 1})
, m_standardAccel({0.3, 0, 1, 1, 1, 1})
, m_standardDecel({0, 0, 0, 1, 1, 1})
, m_expressiveFastSpatial({0.42, 1.67, 0.21, 0.9, 1, 1})
, m_expressiveDefaultSpatial({0.38, 1.21, 0.22, 1, 1, 1})
, m_expressiveSlowSpatial({0.39, 1.29, 0.35, 0.98, 1, 1})
, m_expressiveFastEffects({0.31, 0.94, 0.34, 1, 1, 1})
, m_expressiveDefaultEffects({0.34, 0.8, 0.34, 1, 1, 1})
, m_expressiveSlowEffects({0.34, 0.88, 0.34, 1, 1, 1}) {}
CONFIG_SUBOBJECT(AnimCurves, curves)
CONFIG_SUBOBJECT(RoundingTokens, rounding)
CONFIG_SUBOBJECT(SpacingTokens, spacing)
CONFIG_SUBOBJECT(PaddingTokens, padding)
CONFIG_SUBOBJECT(AnimDurationTokens, animDurations)
};
class AnimDurations : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class BarTokens : public settings::ObjectNode {
CONFIG_NODE(BarTokens, settings::ObjectNode)
CFG_PROPERTY(int, small, 200)
CFG_PROPERTY(int, scale, 1)
CFG_PROPERTY(int, normal, 400)
CFG_PROPERTY(int, large, 600)
CFG_PROPERTY(int, extraLarge, 1000)
CFG_PROPERTY(int, expressiveFastSpatial, 350)
CFG_PROPERTY(int, expressiveDefaultSpatial, 500)
CFG_PROPERTY(int, expressiveSlowSpatial, 650)
CFG_PROPERTY(int, expressiveFastEffects, 150)
CFG_PROPERTY(int, expressiveDefaultEffects, 200)
CFG_PROPERTY(int, expressiveSlowEffects, 300)
public:
explicit AnimDurations(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(int, innerSize, 28)
};
class AnimTokens : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class DashboardTokens : public settings::ObjectNode {
CONFIG_NODE(DashboardTokens, settings::ObjectNode)
CONFIG_SUBOBJECT(AnimDurations, durations)
Q_PROPERTY(QEasingCurve emphasized READ emphasized NOTIFY curvesChanged)
Q_PROPERTY(
QEasingCurve emphasizedAccel READ emphasizedAccel NOTIFY curvesChanged)
Q_PROPERTY(
QEasingCurve emphasizedDecel READ emphasizedDecel NOTIFY curvesChanged)
Q_PROPERTY(QEasingCurve standard READ standard NOTIFY curvesChanged)
Q_PROPERTY(
QEasingCurve standardAccel READ standardAccel NOTIFY curvesChanged)
Q_PROPERTY(
QEasingCurve standardDecel READ standardDecel NOTIFY curvesChanged)
Q_PROPERTY(
QEasingCurve expressiveFastSpatial READ expressiveFastSpatial NOTIFY
curvesChanged)
Q_PROPERTY(
QEasingCurve expressiveDefaultSpatial READ expressiveDefaultSpatial
NOTIFY curvesChanged)
Q_PROPERTY(
QEasingCurve expressiveSlowSpatial READ expressiveSlowSpatial NOTIFY
curvesChanged)
Q_PROPERTY(
QEasingCurve expressiveFastEffects READ expressiveFastEffects NOTIFY
curvesChanged)
Q_PROPERTY(
QEasingCurve expressiveDefaultEffects READ expressiveDefaultEffects
NOTIFY curvesChanged)
Q_PROPERTY(
QEasingCurve expressiveSlowEffects READ expressiveSlowEffects NOTIFY
curvesChanged)
public:
explicit AnimTokens(QObject* parent = nullptr)
: ConfigObject(parent)
, m_curves(new AnimCurves(this))
, m_durations(new AnimDurations(this))
, m_easing(new AnimEasing(this)) {
connect(
m_easing,
&AnimEasing::curvesChanged,
this,
&AnimTokens::curvesChanged);
m_easing->bindCurves(m_curves);
}
[[nodiscard]] QEasingCurve emphasized() const {
return m_easing->emphasized();
}
[[nodiscard]] QEasingCurve emphasizedAccel() const {
return m_easing->emphasizedAccel();
}
[[nodiscard]] QEasingCurve emphasizedDecel() const {
return m_easing->emphasizedDecel();
}
[[nodiscard]] QEasingCurve standard() const { return m_easing->standard(); }
[[nodiscard]] QEasingCurve standardAccel() const {
return m_easing->standardAccel();
}
[[nodiscard]] QEasingCurve standardDecel() const {
return m_easing->standardDecel();
}
[[nodiscard]] QEasingCurve expressiveFastSpatial() const {
return m_easing->expressiveFastSpatial();
}
[[nodiscard]] QEasingCurve expressiveDefaultSpatial() const {
return m_easing->expressiveDefaultSpatial();
}
[[nodiscard]] QEasingCurve expressiveSlowSpatial() const {
return m_easing->expressiveSlowSpatial();
}
[[nodiscard]] QEasingCurve expressiveFastEffects() const {
return m_easing->expressiveFastEffects();
}
[[nodiscard]] QEasingCurve expressiveDefaultEffects() const {
return m_easing->expressiveDefaultEffects();
}
[[nodiscard]] QEasingCurve expressiveSlowEffects() const {
return m_easing->expressiveSlowEffects();
}
signals:
void curvesChanged();
private:
AnimCurves* m_curves;
AnimEasing* m_easing;
CONFIG_PROPERTY(int, processesWidth, 1200)
CONFIG_PROPERTY(int, processesHeight, 800)
CONFIG_PROPERTY(int, processesHeaderHeight, 40)
CONFIG_PROPERTY(int, processesRowHeight, 40)
CONFIG_PROPERTY(int, dateTimeWidth, 110)
CONFIG_PROPERTY(int, infoIconSize, 25)
CONFIG_PROPERTY(int, infoWidth, 200)
CONFIG_PROPERTY(int, mediaCoverArtSize, 150)
CONFIG_PROPERTY(int, mediaProgressSweep, 180)
CONFIG_PROPERTY(int, mediaProgressThickness, 8)
CONFIG_PROPERTY(int, mediaVisualizerSize, 200)
CONFIG_PROPERTY(int, mediaWidth, 200)
CONFIG_PROPERTY(int, resourceProgessThickness, 10)
CONFIG_PROPERTY(int, resourceSize, 200)
CONFIG_PROPERTY(int, tabIndicatorHeight, 3)
CONFIG_PROPERTY(int, tabIndicatorSpacing, 5)
CONFIG_PROPERTY(int, weatherWidth, 250)
};
class BarTokens : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class SizeTokens : public settings::ObjectNode {
CONFIG_NODE(SizeTokens, settings::ObjectNode)
CFG_PROPERTY(int, innerSize, 28)
public:
explicit BarTokens(QObject* parent = nullptr) : ConfigObject(parent) {}
};
class Tokens : public ConfigObject {
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
CONFIG_SUBOBJECT(AppearanceRounding, rounding)
CONFIG_SUBOBJECT(AppearancePadding, padding)
CONFIG_SUBOBJECT(AppearanceSpacing, spacing)
CONFIG_SUBOBJECT(FontTokens, font)
CONFIG_SUBOBJECT(AnimTokens, anim)
CONFIG_SUBOBJECT(DashboardTokens, dashboard)
CONFIG_SUBOBJECT(BarTokens, bar)
public:
explicit Tokens(QObject* parent = nullptr)
: ConfigObject(parent)
, m_rounding(new AppearanceRounding(this))
, m_padding(new AppearancePadding(this))
, m_spacing(new AppearanceSpacing(this))
, m_font(new FontTokens(this))
, m_anim(new AnimTokens(this))
, m_bar(new BarTokens(this)) {}
static Tokens* create(QQmlEngine*, QJSEngine*) { return new Tokens(); }
};
} // namespace ZShell::config
+36 -55
View File
@@ -1,7 +1,6 @@
#pragma once
#include "configobject.hpp"
#include "configlist.hpp"
#include "common.hpp"
#include <QVariantList>
#include <QStringList>
@@ -9,75 +8,57 @@
namespace ZShell::config {
class UtilitiesSizes : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class UtilitiesSizes : public settings::ObjectNode {
CONFIG_NODE(UtilitiesSizes, settings::ObjectNode)
CFG_PROPERTY(int, toastWidth, 430)
CFG_PROPERTY(int, width, 430)
public:
explicit UtilitiesSizes(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(int, toastWidth, 430)
CONFIG_PROPERTY(int, width, 430)
};
class Toasts : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Toasts : public settings::ObjectNode {
CONFIG_NODE(Toasts, settings::ObjectNode)
CFG_PROPERTY(bool, audioInputChanged, true)
CFG_PROPERTY(bool, audioOutputChanged, true)
CFG_PROPERTY(bool, capsLockChanged, true)
CFG_PROPERTY(bool, chargingChanged, true)
CFG_PROPERTY(bool, configLoaded, true)
CFG_PROPERTY(bool, dndChanged, true)
CFG_PROPERTY(bool, gameModeChanged, true)
CFG_PROPERTY(bool, kbLayoutChanged, true)
CFG_PROPERTY(bool, kbLimit, true)
CFG_PROPERTY(bool, nowPlaying, false)
CFG_PROPERTY(bool, numLockChanged, true)
CFG_PROPERTY(bool, vpnChanged, true)
public:
explicit Toasts(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(bool, audioInputChanged, true)
CONFIG_PROPERTY(bool, audioOutputChanged, true)
CONFIG_PROPERTY(bool, capsLockChanged, true)
CONFIG_PROPERTY(bool, chargingChanged, true)
CONFIG_PROPERTY(bool, configLoaded, true)
CONFIG_PROPERTY(bool, dndChanged, true)
CONFIG_PROPERTY(bool, gameModeChanged, true)
CONFIG_PROPERTY(bool, kbLayoutChanged, true)
CONFIG_PROPERTY(bool, kbLimit, true)
CONFIG_PROPERTY(bool, nowPlaying, false)
CONFIG_PROPERTY(bool, numLockChanged, true)
CONFIG_PROPERTY(bool, vpnChanged, true)
};
class Vpn : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Vpn : public settings::ObjectNode {
CONFIG_NODE(Vpn, settings::ObjectNode)
CFG_PROPERTY(bool, enabled, false)
CFG_PROPERTY(QStringList, provider, (QStringList{QStringLiteral("netbird")}))
public:
explicit Vpn(QObject* parent = nullptr) : ConfigObject(parent) {}
CONFIG_PROPERTY(bool, enabled, false)
CONFIG_PROPERTY(
QStringList, provider, (QStringList{QStringLiteral("netbird")}))
};
class Utilities : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
class Utilities : public settings::ObjectNode {
CONFIG_NODE(Utilities, settings::ObjectNode)
CFG_PROPERTY(bool, enabled, true)
CFG_PROPERTY(int, maxToasts, 4)
CONFIG_PROPERTY(bool, enabled, true)
CONFIG_PROPERTY(int, maxToasts, 4)
CONFIG_SUBOBJECT(UtilitiesSizes, sizes)
CONFIG_SUBOBJECT(Toasts, toasts)
CONFIG_SUBOBJECT(Vpn, vpn)
CONFIG_LIST(
EntryList,
quickToggles,
{LIST_ENTRY(dnd, true),
LIST_ENTRY(wifi, true),
LIST_ENTRY(settings, true),
LIST_ENTRY(bluetooth, true),
LIST_ENTRY(mic, true),
LIST_ENTRY(audio, true),
LIST_ENTRY(gameMode, true)})
public:
explicit Utilities(QObject* parent = nullptr)
: ConfigObject(parent)
, m_sizes(new UtilitiesSizes(this))
, m_toasts(new Toasts(this))
, m_vpn(new Vpn(this)) {}
DEFAULT_ARG(
{LIST_ENTRY(dnd, true),
LIST_ENTRY(wifi, true),
LIST_ENTRY(settings, true),
LIST_ENTRY(bluetooth, true),
LIST_ENTRY(mic, true),
LIST_ENTRY(audio, true),
LIST_ENTRY(gameMode, true)}))
};
} // namespace ZShell::config