feat: add role-based font tokens

This commit is contained in:
2 * r + 2 * t 2026-04-20 23:35:34 +10:00
parent 1f2913f90c
commit f2575b3331
9 changed files with 425 additions and 73 deletions

View file

@ -6,6 +6,7 @@ qml_module(caelestia-config
configobject.cpp
rootconfig.cpp
appearanceconfig.cpp
font.cpp
fontbuilder.cpp
tokens.cpp
tokensattached.cpp

View file

@ -142,37 +142,6 @@ int AppearancePadding::extraExtraLarge() const {
return m_tokens ? static_cast<int>(m_tokens->extraExtraLarge() * m_scale) : 0;
}
// FontSize
void FontSize::bindTokens(FontSizeTokens* tokens) {
m_tokens = tokens;
connectTokenSignals(tokens, this);
}
int FontSize::small() const {
return m_tokens ? static_cast<int>(m_tokens->small() * m_scale) : 0;
}
int FontSize::smaller() const {
return m_tokens ? static_cast<int>(m_tokens->smaller() * m_scale) : 0;
}
int FontSize::normal() const {
return m_tokens ? static_cast<int>(m_tokens->normal() * m_scale) : 0;
}
int FontSize::larger() const {
return m_tokens ? static_cast<int>(m_tokens->larger() * m_scale) : 0;
}
int FontSize::large() const {
return m_tokens ? static_cast<int>(m_tokens->large() * m_scale) : 0;
}
int FontSize::extraLarge() const {
return m_tokens ? static_cast<int>(m_tokens->extraLarge() * m_scale) : 0;
}
// AnimDurations
void AnimDurations::bindTokens(AnimDurationTokens* tokens) {

View file

@ -2,7 +2,9 @@
#include "configobject.hpp"
#include <qfont.h>
#include <qstring.h>
#include <qvariant.h>
namespace caelestia::config {
@ -10,7 +12,6 @@ namespace caelestia::config {
class RoundingTokens;
class SpacingTokens;
class PaddingTokens;
class FontSizeTokens;
class AnimDurationTokens;
class AppearanceRounding : public ConfigObject {
@ -126,65 +127,70 @@ private:
PaddingTokens* m_tokens = nullptr;
};
class FontFamily : public ConfigObject {
class FontConfig : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
CONFIG_PROPERTY(QString, sans, QStringLiteral("Rubik"))
CONFIG_PROPERTY(QString, mono, QStringLiteral("CaskaydiaCove NF"))
CONFIG_PROPERTY(QString, material, QStringLiteral("Material Symbols Rounded"))
CONFIG_PROPERTY(QString, clock, QStringLiteral("Rubik"))
CONFIG_PROPERTY(QString, family, QStringLiteral("Rubik"))
CONFIG_PROPERTY(int, size, 14)
CONFIG_PROPERTY(int, weight, QFont::Normal)
CONFIG_PROPERTY(bool, italic, false)
CONFIG_PROPERTY(QVariantMap, vaxes, {})
public:
explicit FontFamily(QObject* parent = nullptr)
explicit FontConfig(QObject* parent = nullptr)
: ConfigObject(parent) {}
};
class FontSize : public ConfigObject {
class FontStyleConfig : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
CONFIG_PROPERTY(qreal, scale, 1)
Q_PROPERTY(int small READ small NOTIFY valuesChanged)
Q_PROPERTY(int smaller READ smaller NOTIFY valuesChanged)
Q_PROPERTY(int normal READ normal NOTIFY valuesChanged)
Q_PROPERTY(int larger READ larger NOTIFY valuesChanged)
Q_PROPERTY(int large READ large NOTIFY valuesChanged)
Q_PROPERTY(int extraLarge READ extraLarge NOTIFY valuesChanged)
CONFIG_SUBOBJECT(FontConfig, large)
CONFIG_SUBOBJECT(FontConfig, medium)
CONFIG_SUBOBJECT(FontConfig, small)
public:
explicit FontSize(QObject* parent = nullptr)
: ConfigObject(parent) {}
explicit FontStyleConfig(QObject* parent = nullptr)
: ConfigObject(parent)
, m_large(new FontConfig(this))
, m_medium(new FontConfig(this))
, m_small(new FontConfig(this)) {}
};
void bindTokens(FontSizeTokens* tokens);
class IconFontStyleConfig : public FontStyleConfig {
Q_OBJECT
QML_ANONYMOUS
[[nodiscard]] int small() const;
[[nodiscard]] int smaller() const;
[[nodiscard]] int normal() const;
[[nodiscard]] int larger() const;
[[nodiscard]] int large() const;
[[nodiscard]] int extraLarge() const;
CONFIG_SUBOBJECT(FontConfig, extraLarge)
signals:
void valuesChanged();
private:
FontSizeTokens* m_tokens = nullptr;
public:
explicit IconFontStyleConfig(QObject* parent = nullptr)
: FontStyleConfig(parent)
, m_extraLarge(new FontConfig(this)) {}
};
class AppearanceFont : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
CONFIG_SUBOBJECT(FontFamily, family)
CONFIG_SUBOBJECT(FontSize, size)
CONFIG_SUBOBJECT(FontStyleConfig, headline)
CONFIG_SUBOBJECT(FontStyleConfig, title)
CONFIG_SUBOBJECT(FontStyleConfig, body)
CONFIG_SUBOBJECT(FontStyleConfig, label)
CONFIG_SUBOBJECT(FontStyleConfig, mono)
CONFIG_SUBOBJECT(IconFontStyleConfig, icon)
CONFIG_PROPERTY(QString, clock, QStringLiteral("Rubik"))
public:
explicit AppearanceFont(QObject* parent = nullptr)
: ConfigObject(parent)
, m_family(new FontFamily(this))
, m_size(new FontSize(this)) {}
, m_headline(new FontStyleConfig(this))
, m_title(new FontStyleConfig(this))
, m_body(new FontStyleConfig(this))
, m_label(new FontStyleConfig(this))
, m_mono(new FontStyleConfig(this))
, m_icon(new IconFontStyleConfig(this)) {}
};
class AnimDurations : public ConfigObject {

View file

@ -103,7 +103,6 @@ void GlobalConfig::bindAppearanceTokens() {
m_appearance->rounding()->bindTokens(tokenAppearance->rounding());
m_appearance->spacing()->bindTokens(tokenAppearance->spacing());
m_appearance->padding()->bindTokens(tokenAppearance->padding());
m_appearance->font()->size()->bindTokens(tokenAppearance->fontSize());
m_appearance->anim()->durations()->bindTokens(tokenAppearance->animDurations());
m_tokensBound = true;
}

View file

@ -0,0 +1,218 @@
#include "font.hpp"
#include "appearanceconfig.hpp"
namespace caelestia::config {
// FontStyleBase
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 FontConfig* cfg) {
QFont font;
font.setFamily(cfg->family());
font.setPointSize(cfg->size());
font.setWeight(QFont::Weight(cfg->weight()));
font.setItalic(cfg->italic());
const auto axes = cfg->vaxes();
for (auto it = axes.constBegin(); it != axes.constEnd(); ++it) {
if (auto tag = QFont::Tag::fromString(it.key()))
font.setVariableAxis(*tag, it.value().toFloat());
}
return font;
}
void FontStyleBase::bind(FontStyleConfig* cfg) {
if (m_cfg == cfg)
return;
if (m_cfg) {
disconnect(m_cfg->large(), nullptr, this, nullptr);
disconnect(m_cfg->medium(), nullptr, this, nullptr);
disconnect(m_cfg->small(), nullptr, this, nullptr);
}
m_cfg = cfg;
if (cfg) {
connect(cfg->large(), &ConfigObject::propertiesChanged, this, &FontStyleBase::rebuild);
connect(cfg->medium(), &ConfigObject::propertiesChanged, this, &FontStyleBase::rebuild);
connect(cfg->small(), &ConfigObject::propertiesChanged, this, &FontStyleBase::rebuild);
}
rebuild();
}
void FontStyleBase::rebuild() {
if (m_cfg) {
m_large = buildFont(m_cfg->large());
m_medium = buildFont(m_cfg->medium());
m_small = buildFont(m_cfg->small());
} else {
m_large = QFont();
m_medium = QFont();
m_small = QFont();
}
emit fontsChanged();
}
// FontStyle
FontStyle::FontStyle(QObject* parent)
: FontStyleBase(parent)
, m_builders(new FontBuilders(this, this)) {}
FontBuilders* FontStyle::builders() const {
return m_builders;
}
// IconFontStyle
void IconFontStyle::bind(IconFontStyleConfig* cfg) {
if (m_cfg == cfg)
return;
if (m_cfg) {
// Previous cfg may or may not be an IconFontStyleConfig; guard via cast.
if (auto* prev = qobject_cast<IconFontStyleConfig*>(m_cfg))
disconnect(prev->extraLarge(), nullptr, this, nullptr);
}
FontStyleBase::bind(cfg);
if (cfg)
connect(cfg->extraLarge(), &ConfigObject::propertiesChanged, this, &IconFontStyle::rebuild);
}
QFont IconFontStyle::extraLarge() const {
return m_extraLarge;
}
FontBuilder IconFontStyle::builder() const {
return FontBuilder(m_large);
}
void IconFontStyle::rebuild() {
if (auto* cfg = qobject_cast<IconFontStyleConfig*>(m_cfg)) {
m_large = buildFont(cfg->large());
m_medium = buildFont(cfg->medium());
m_small = buildFont(cfg->small());
m_extraLarge = buildFont(cfg->extraLarge());
} else {
m_large = QFont();
m_medium = QFont();
m_small = QFont();
m_extraLarge = QFont();
}
emit fontsChanged();
}
// FontBuilders
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());
}
// FontTokens
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)) {}
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);
}
void FontTokens::bindFont(AppearanceFont* font) {
if (m_font == font)
return;
if (m_font)
disconnect(m_font, nullptr, this, nullptr);
m_font = font;
if (font) {
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);
} else {
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();
}
void FontTokens::rebuildClock() {
QFont f;
if (m_font)
f.setFamily(m_font->clock());
m_clock = f;
emit clockChanged();
}
} // namespace caelestia::config

View file

@ -0,0 +1,147 @@
#pragma once
#include "fontbuilder.hpp"
#include <qqmlintegration.h>
namespace caelestia::config {
class AppearanceFont;
class FontConfig;
class FontStyleConfig;
class IconFontStyleConfig;
class FontStyleBase;
class FontBuilders : public QObject {
Q_OBJECT
QML_ANONYMOUS
Q_PROPERTY(caelestia::config::FontBuilder large READ large NOTIFY buildersChanged FINAL)
Q_PROPERTY(caelestia::config::FontBuilder medium READ medium NOTIFY buildersChanged FINAL)
Q_PROPERTY(caelestia::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();
private:
const FontStyleBase* m_style;
};
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)
: QObject(parent) {}
void bind(FontStyleConfig* cfg);
[[nodiscard]] QFont large() const;
[[nodiscard]] QFont medium() const;
[[nodiscard]] QFont small() const;
signals:
void fontsChanged();
protected:
virtual void rebuild();
static QFont buildFont(const FontConfig* cfg);
FontStyleConfig* m_cfg = nullptr;
QFont m_large;
QFont m_medium;
QFont m_small;
};
class FontStyle : public FontStyleBase {
Q_OBJECT
QML_ANONYMOUS
Q_PROPERTY(caelestia::config::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(caelestia::config::FontBuilder builder READ builder NOTIFY fontsChanged FINAL)
public:
explicit IconFontStyle(QObject* parent = nullptr)
: FontStyleBase(parent) {}
void bind(IconFontStyleConfig* cfg);
[[nodiscard]] QFont extraLarge() const;
[[nodiscard]] FontBuilder builder() const;
protected:
void rebuild() override;
private:
QFont m_extraLarge;
};
class FontTokens : public QObject {
Q_OBJECT
QML_ANONYMOUS
Q_PROPERTY(caelestia::config::FontStyle* headline READ headline CONSTANT FINAL)
Q_PROPERTY(caelestia::config::FontStyle* title READ title CONSTANT FINAL)
Q_PROPERTY(caelestia::config::FontStyle* body READ body CONSTANT FINAL)
Q_PROPERTY(caelestia::config::FontStyle* label READ label CONSTANT FINAL)
Q_PROPERTY(caelestia::config::FontStyle* mono READ mono CONSTANT FINAL)
Q_PROPERTY(caelestia::config::IconFontStyle* icon READ icon CONSTANT FINAL)
Q_PROPERTY(caelestia::config::FontBuilder clock READ clock NOTIFY clockChanged 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;
signals:
void clockChanged();
private:
void rebuildClock();
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 caelestia::config

View file

@ -1,8 +1,5 @@
#include "fontbuilder.hpp"
#include <qcoreapplication.h>
#include <qmetatype.h>
namespace caelestia::config {
FontBuilder::FontBuilder(QFont font)

View file

@ -2,6 +2,7 @@
#include "anim.hpp"
#include "appearanceconfig.hpp"
#include "config.hpp"
#include "font.hpp"
#include "monitorconfigmanager.hpp"
#include "tokens.hpp"
@ -23,8 +24,10 @@ const AppearanceConfig* resolveAppearance(GlobalConfig* config, bool complete, c
Tokens::Tokens(QObject* parent)
: QQuickAttachedPropertyPropagator(parent)
, m_font(new FontTokens(this))
, m_anim(new AnimTokens(this)) {
bindAnim();
bindFont();
initialize();
}
@ -52,6 +55,7 @@ void Tokens::inheritScreen(const QString& screen) {
m_tokens = MonitorConfigManager::instance()->tokensForScreen(m_screen);
}
bindFont();
propagateScreen();
emit sourceChanged();
}
@ -78,6 +82,11 @@ void Tokens::bindAnim() {
m_anim->bindCurves(TokenConfig::instance()->appearance()->curves());
}
void Tokens::bindFont() {
auto* appearance = m_config ? m_config->appearance() : GlobalConfig::instance()->appearance();
m_font->bindFont(appearance->font());
}
#define TOKENS_ATTACHED_GETTER(Type, name) \
const Type* Tokens::name() const { \
auto* a = resolveAppearance(m_config, m_complete, #name, parent()); \
@ -87,7 +96,6 @@ void Tokens::bindAnim() {
TOKENS_ATTACHED_GETTER(AppearanceRounding, rounding)
TOKENS_ATTACHED_GETTER(AppearanceSpacing, spacing)
TOKENS_ATTACHED_GETTER(AppearancePadding, padding)
TOKENS_ATTACHED_GETTER(AppearanceFont, font)
#undef TOKENS_ATTACHED_GETTER
@ -103,6 +111,10 @@ const SizeTokens* Tokens::sizes() const {
return TokenConfig::instance()->sizes();
}
const FontTokens* Tokens::font() const {
return m_font;
}
const AnimTokens* Tokens::anim() const {
return m_anim;
}

View file

@ -7,11 +7,11 @@
namespace caelestia::config {
class AnimTokens;
class AppearanceFont;
class AppearancePadding;
class AppearanceRounding;
class AppearanceSpacing;
class AppearanceTransparency;
class FontTokens;
class GlobalConfig;
class SizeTokens;
class TokenConfig;
@ -24,15 +24,16 @@ class Tokens : public QQuickAttachedPropertyPropagator, public QQmlParserStatus
QML_ATTACHED(Tokens)
Q_MOC_INCLUDE("anim.hpp")
Q_MOC_INCLUDE("appearanceconfig.hpp")
Q_MOC_INCLUDE("font.hpp")
Q_MOC_INCLUDE("tokens.hpp")
Q_PROPERTY(QString screen READ screen WRITE inheritScreen NOTIFY sourceChanged)
Q_PROPERTY(const caelestia::config::AppearanceRounding* rounding READ rounding NOTIFY sourceChanged)
Q_PROPERTY(const caelestia::config::AppearanceSpacing* spacing READ spacing NOTIFY sourceChanged)
Q_PROPERTY(const caelestia::config::AppearancePadding* padding READ padding NOTIFY sourceChanged)
Q_PROPERTY(const caelestia::config::AppearanceFont* font READ font NOTIFY sourceChanged)
Q_PROPERTY(const caelestia::config::AppearanceTransparency* transparency READ transparency NOTIFY sourceChanged)
Q_PROPERTY(const caelestia::config::SizeTokens* sizes READ sizes NOTIFY sourceChanged)
Q_PROPERTY(const caelestia::config::FontTokens* font READ font NOTIFY sourceChanged)
Q_PROPERTY(const caelestia::config::AnimTokens* anim READ anim NOTIFY sourceChanged)
public:
@ -44,10 +45,10 @@ public:
[[nodiscard]] const AppearanceRounding* rounding() const;
[[nodiscard]] const AppearanceSpacing* spacing() const;
[[nodiscard]] const AppearancePadding* padding() const;
[[nodiscard]] const AppearanceFont* font() const;
[[nodiscard]] const AppearanceTransparency* transparency() const;
[[nodiscard]] const SizeTokens* sizes() const;
[[nodiscard]] const FontTokens* font() const;
[[nodiscard]] const AnimTokens* anim() const;
[[nodiscard]] Q_INVOKABLE static TokenConfig* forScreen(const QString& screen);
@ -67,11 +68,13 @@ private:
void propagateScreen();
void bindAnim();
void bindFont();
bool m_complete = false;
QString m_screen;
GlobalConfig* m_config = nullptr;
TokenConfig* m_tokens = nullptr;
FontTokens* m_font = nullptr;
AnimTokens* m_anim = nullptr;
};