From a7767d2a8003301db4d065ddffd2bb12a1b8ab8e Mon Sep 17 00:00:00 2001 From: zach Date: Sat, 7 Nov 2026 14:42:22 +0100 Subject: [PATCH] fix: font macro expansion + snaplayout jitter with fillWidth: true --- Modules/Bar/Components/Clock.qml | 5 +- Plugins/ZShell/Components/snaplayout.cpp | 341 ++++++++++++++++------- Plugins/ZShell/Config/CMakeLists.txt | 2 + Plugins/ZShell/Config/appearance.hpp | 37 +-- Plugins/ZShell/Config/font.cpp | 57 ++-- Plugins/ZShell/Config/font.hpp | 26 +- 6 files changed, 296 insertions(+), 172 deletions(-) diff --git a/Modules/Bar/Components/Clock.qml b/Modules/Bar/Components/Clock.qml index ec7bda6..76650c7 100644 --- a/Modules/Bar/Components/Clock.qml +++ b/Modules/Bar/Components/Clock.qml @@ -1,6 +1,5 @@ pragma ComponentBehavior: Bound -import Quickshell import QtQuick import QtQuick.Layouts import ZShell.Config @@ -101,7 +100,7 @@ WidgetBase { Layout.bottomMargin: Tokens.padding.extraSmall ?? 2 Layout.topMargin: Tokens.padding.extraSmall ?? 2 color: root.contentColor - height: 1 + implicitHeight: 1 implicitWidth: root.baseSize * 0.5 opacity: 0.4 radius: 1 @@ -123,7 +122,7 @@ WidgetBase { if (modelData.includes("m")) return Time.minuteStr; if (modelData.includes("s")) - return Time.secondStr; + return Time.secondStr.split(" ")[0]; return ""; } diff --git a/Plugins/ZShell/Components/snaplayout.cpp b/Plugins/ZShell/Components/snaplayout.cpp index 7b4badb..ed01aed 100644 --- a/Plugins/ZShell/Components/snaplayout.cpp +++ b/Plugins/ZShell/Components/snaplayout.cpp @@ -172,6 +172,10 @@ QSizeF SnapLayout::sizeHint(Qt::SizeHint whichSizeHint) const { if (dpr <= 0.0) return engineHint; const bool horiz = d->orientation == Qt::Horizontal; + // Which way the children are traversed, so the leading and trailing margin + // of a child are the sides the arrangement measures its gap from. + const int sgn = horiz && effectiveLayoutDirection() == Qt::RightToLeft ? -1 + : 1; // Qt builds implicit sizes out of whole logical pixels: // QQuickLayout::effectiveSizeHints_helper takes `qCeil(item->implicitHeight())` @@ -239,10 +243,29 @@ QSizeF SnapLayout::sizeHint(Qt::SizeHint whichSizeHint) const { // Rounding up keeps the hint from ever under-sizing its contents. const qint64 mainDev = qint64(std::ceil(mainHint * dpr - 1e-9)); - const qint64 mainMarginDev = qint64(std::ceil(mainMargin * dpr - 1e-9)); + // Every margin side is rounded on its own, exactly as the arrangement + // rounds it when it places the child. Rounding the two sides of a child + // together can leave the hint a device pixel short of what is written - + // two one-pixel margins are three device pixels at 1.5x when summed but + // four when rounded separately - and a Flickable that takes this hint as + // its content height would then have bounds that cut into the content. + const qreal mainLead = + horiz ? (sgn > 0 ? margins.left() : margins.right()) + : margins.top(); + const qreal mainTrail = + horiz ? (sgn > 0 ? margins.right() : margins.left()) + : margins.bottom(); + const qint64 mainMarginDev = + std::llround(mainLead * dpr) + std::llround(mainTrail * dpr); + const qreal crossLead = + horiz ? margins.top() + : (sgn > 0 ? margins.left() : margins.right()); + const qreal crossTrail = + horiz ? margins.bottom() + : (sgn > 0 ? margins.right() : margins.left()); const qint64 crossDevForChild = qint64(std::ceil(crossHint * dpr - 1e-9)) + - qint64(std::ceil(crossMargin * dpr - 1e-9)); + std::llround(crossLead * dpr) + std::llround(crossTrail * dpr); mainExtentDev += mainDev + mainMarginDev; maxMainDev = (std::max)(maxMainDev, mainDev + mainMarginDev); @@ -598,11 +621,6 @@ void SnapLayout::arrangeChildren() { const qint64 originXDev = std::llround(origin.x() * dpr); const qint64 originYDev = std::llround(origin.y() * dpr); - qreal prevEndExact = 0.0; - qint64 prevNearDev = 0; - qint64 prevSizeDev = 0; - bool started = false; - // The layout's own main extent, rounded down: no child is placed past it, so // a Flickable that takes this layout's height as its content height can still // reach the last child. @@ -610,6 +628,60 @@ void SnapLayout::arrangeChildren() { const qint64 layoutMainExtentDev = qint64(std::floor((horiz ? self.width() : self.height()) * dpr + 1e-6)); + // An aligned child whose cell is its own implicit size is sized from that + // implicit size. The engine recomputes cells from animated geometry, so a + // cell that is mathematically 41 can arrive as 40.9999999999; when the + // device pixel ratio puts 41 exactly on half a device pixel, that epsilon + // flips the rounding from pass to pass and the child visibly resizes while + // it only moves. Children that fill their axis, or whose cell has grown + // past their implicit size, keep the engine-derived size so that animated + // fills still step through device pixels. + const auto snappedOwn = [&](bool fills, qreal cellSize, qreal ownSize) { + if (fills || ownSize <= 0.0 || qAbs(cellSize - ownSize) >= 1.0) + return qint64(-1); + // Round up, like Qt's `qCeil` of implicit sizes: a child is never + // smaller than the content it holds, and this is the same rounding + // sizeHint() sums to compute the layout's own preferred size, so the + // children fill the layout without slack and without overflow. + return (std::max)(qint64(std::ceil(ownSize * dpr - 1e-9)), qint64(0)); + }; + + struct Row { + QQuickItem* child = nullptr; + qreal mainExact = 0.0; // engine start, device pixels + qreal mainSizeExact = 0.0; // engine main size, device pixels + qint64 mainFarExact = 0; // engine end, device pixels + qint64 crossExact = 0; // engine cross start, device pixels + qint64 engineWidth = 0; + qint64 engineHeight = 0; + qint64 mainOwnDev = -1; + qint64 crossOwnDev = -1; + qint64 marginLeadDev = + 0; // Layout margin before the child, device pixels + qint64 marginTrailDev = + 0; // Layout margin after the child, device pixels + bool fillsMain = false; + qint64 pos = 0; // leading edge in traversal device pixels + qint64 size = 0; // main size to write + qint64 cross = 0; // cross size to write + }; + + // Traversal coordinates run along the main axis in child order: left to right + // unless the layout is mirrored. A child's `pos` is its leading edge in that + // direction, so it spans [pos, pos + size] whichever way the layout runs, and + // the chain and the anchoring below are direction-free. + const int sgn = horiz && effectiveLayoutDirection() == Qt::RightToLeft ? -1 + : 1; + + // Spacing is normalized to whole device pixels, and each child's Layout + // margins are rounded on their own rather than together with the spacing: + // rounding the sum makes the same spacing read as 7 or 8 device pixels + // depending on which two children it sits between. + const qint64 spacingDev = std::llround(spacing() * dpr); + + QVector rows; + rows.reserve(d->engine.itemCount()); + for (int i = 0; i < d->engine.itemCount(); ++i) { SnapGridLayoutItem* gridItem = static_cast(d->engine.itemAt(i)); @@ -620,129 +692,190 @@ void SnapLayout::arrangeChildren() { const qreal mainExact = (horiz ? cell.x() : cell.y()) * dpr; const qreal mainSizeExact = (horiz ? cell.width() : cell.height()) * dpr; - const qreal crossExact = (horiz ? cell.y() : cell.x()) * dpr; + + Row row; + row.child = child; + row.mainExact = mainExact; + row.mainSizeExact = mainSizeExact; + row.mainFarExact = std::llround(mainExact + mainSizeExact); + row.crossExact = std::llround((horiz ? cell.y() : cell.x()) * dpr); + const qint64 mainSizeDev = std::llround(mainSizeExact); const qint64 crossSizeDev = std::llround((horiz ? cell.height() : cell.width()) * dpr); - const qint64 engineWidth = horiz ? mainSizeDev : crossSizeDev; - const qint64 engineHeight = horiz ? crossSizeDev : mainSizeDev; + row.engineWidth = horiz ? mainSizeDev : crossSizeDev; + row.engineHeight = horiz ? crossSizeDev : mainSizeDev; - qint64 mainNear = 0; - if (!started) { - mainNear = std::llround(mainExact); - started = true; - } else { - mainNear = prevNearDev + prevSizeDev + - std::llround(mainExact - prevEndExact); - } - - const qint64 mainFarExact = std::llround(mainExact + mainSizeExact); - - // An aligned child whose cell is its own implicit size is sized from that - // implicit size. The engine recomputes cells from animated geometry, so a - // cell that is mathematically 41 can arrive as 40.9999999999; when the - // device pixel ratio puts 41 exactly on half a device pixel, that epsilon - // flips the rounding from pass to pass and the child visibly resizes while - // it only moves. Children that fill their axis, or whose cell has grown - // past their implicit size, keep the engine-derived size so that animated - // fills still step through device pixels. QQuickLayoutAttached* info = attachedLayoutObject(child, false); const qreal mainOwn = horiz ? child->implicitWidth() : child->implicitHeight(); const qreal crossOwn = horiz ? child->implicitHeight() : child->implicitWidth(); - const bool fillsMain = info && - (horiz ? info->fillWidth() : info->fillHeight()); + row.fillsMain = info && + (horiz ? info->fillWidth() : info->fillHeight()); const bool fillsCross = info && (horiz ? info->fillHeight() : info->fillWidth()); - - const auto snappedOwn = [&](bool fills, qreal cellSize, qreal ownSize) { - if (fills || ownSize <= 0.0 || qAbs(cellSize - ownSize) >= 1.0) - return qint64(-1); - // Round up, like Qt's `qCeil` of implicit sizes: a child is never - // smaller than the content it holds, and this is the same rounding - // sizeHint() sums to compute the layout's own preferred size, so the - // children fill the layout without slack and without overflow. - return (std::max)(qint64(std::ceil(ownSize * dpr - 1e-9)), - qint64(0)); - }; - - const qint64 mainOwnDev = - snappedOwn(fillsMain, horiz ? cell.width() : cell.height(), mainOwn); - const qint64 crossOwnDev = snappedOwn( + row.mainOwnDev = snappedOwn( + row.fillsMain, horiz ? cell.width() : cell.height(), mainOwn); + row.crossOwnDev = snappedOwn( fillsCross, horiz ? cell.height() : cell.width(), crossOwn); - // The size along the main axis. An implicit-sized child keeps its own - // implicit size, which carries the device-pixel parity ZUtils chose for - // it. Rounding is not neutral: an odd logical size at 1.5x is exactly half - // a device pixel and always rounds up, so left unchecked every child adds - // half a pixel and a long list ends outside the layout - a Flickable reads - // the layout's height as the end of the content and the last rows become - // unreachable. Corrections are made in steps of two device pixels: an item - // that centers its own content (Layout.HCenter / Layout.VCenter, - // anchors.centerIn) lands on whole device pixels only while its device - // size keeps that parity, so a one-pixel correction moves its centered - // children onto half a pixel and they antialias. Drift from the engine's - // edge is bounded at two device pixels, and no child is placed past the - // layout's own edge - a layout with less room than its children needs - // overflows in Qt too, so the engine's own edge is never pulled back. - const qint64 mainFarCeil = - (std::max)(mainFarExact, layoutMainExtentDev); - const qint64 mainLimit = - (std::min)(mainFarExact + 2, mainFarCeil) - mainNear; + const QMarginsF margins = info ? info->effectiveQMargins() + : QMarginsF(0, 0, 0, 0); + const qreal marginLead = + horiz ? (sgn > 0 ? margins.left() : margins.right()) + : margins.top(); + const qreal marginTrail = + horiz ? (sgn > 0 ? margins.right() : margins.left()) + : margins.bottom(); + row.marginLeadDev = std::llround(marginLead * dpr); + row.marginTrailDev = std::llround(marginTrail * dpr); + rows.append(row); + } - qint64 width = engineWidth; - qint64 height = engineHeight; - const qint64 mainSizeWritten = - mainOwnDev >= 0 ? mainOwnDev - : (std::max)(mainFarExact - mainNear, qint64(0)); - if (horiz) - width = mainSizeWritten; - else - height = mainSizeWritten; + if (rows.isEmpty()) return; - if (crossOwnDev >= 0) { - if (horiz) - height = crossOwnDev; - else - width = crossOwnDev; - } + // The layout's own edges in traversal coordinates. The first child is placed + // against the start edge and the last one back from the end edge, each by its + // own rounded margin - the same rounding the gaps and `sizeHint` use, so the + // layout's implicit size is the size its children actually occupy. Rounding + // the engine's cell edge instead measures the margin from the far end, which + // can land a device pixel away from that. + const qint64 layoutStartDev = sgn > 0 ? 0 : -layoutMainExtentDev; + const qint64 layoutEndDev = sgn > 0 ? layoutMainExtentDev : 0; + // The engine's trailing edge in traversal coordinates: only used to keep an + // over-constrained layout overflowing, as Qt does, instead of pulling its + // last child back inside the box. + const auto engineTrailing = [&](const Row& row) { + return sgn > 0 ? row.mainFarExact : -std::llround(row.mainExact); + }; + + const auto gapDev = [&](int prev, int next) { + return spacingDev + rows.at(prev).marginTrailDev + + rows.at(next).marginLeadDev; + }; + + // Sizes are decided before positions. A gap is measured from the size that + // actually gets written, so spacing stays equal however a child was rounded, + // remembered, or filled. + for (Row& row : rows) { + qint64 main = + row.mainOwnDev >= 0 + ? row.mainOwnDev + : (std::max)(std::llround(row.mainSizeExact), qint64(0)); + qint64 cross = + row.crossOwnDev >= 0 + ? row.crossOwnDev + : (std::max)(horiz ? row.engineHeight : row.engineWidth, + qint64(0)); // Keep the size already written unless the engine asks for a different // one, or something else resized the child. An aligned size stays // aligned wherever the layout moves, and resizing again costs the child // a relayout: an Image reloads its texture, a nested layout rearranges. - const auto stored = d->childSizes.constFind(child); + const auto stored = d->childSizes.constFind(row.child); if (stored != d->childSizes.constEnd() && - stored.value().engineWidth == engineWidth && - stored.value().engineHeight == engineHeight && - std::llround(child->width() * dpr) == stored.value().width && - std::llround(child->height() * dpr) == stored.value().height) { - width = stored.value().width; - height = stored.value().height; + stored.value().engineWidth == row.engineWidth && + stored.value().engineHeight == row.engineHeight && + std::llround(row.child->width() * dpr) == stored.value().width && + std::llround(row.child->height() * dpr) == stored.value().height) { + main = horiz ? stored.value().width : stored.value().height; + cross = horiz ? stored.value().height : stored.value().width; } - // A remembered size can predate the layout being sized, so the limit is - // applied to whatever ends up being written, keeping the parity of the - // size this child is asking for. - qint64& mainAxisSize = horiz ? width : height; - const int mainParity = int(mainAxisSize % 2); - if (mainAxisSize > mainLimit) { - mainAxisSize = mainLimit; - if (int(mainAxisSize % 2) != mainParity) --mainAxisSize; - } - mainAxisSize = (std::max)(mainAxisSize, qint64(0)); + row.size = (std::max)(main, qint64(0)); + row.cross = (std::max)(cross, qint64(0)); + } + // The first child that fills the main axis absorbs every change in front of + // it, and every child behind it is measured back from the layout's own end + // edge. A chain that runs through a fill would otherwise carry a leading + // child's rounding - and its size change - into children that are supposed + // to sit still against the far edge. + int fillIdx = int(rows.size()); + for (int i = 0; i < int(rows.size()); ++i) { + if (rows.at(i).fillsMain) { + fillIdx = i; + break; + } + } + + qint64 prevPos = 0; + qint64 prevSize = 0; + const int leadEnd = (std::min)(fillIdx, int(rows.size()) - 1); + for (int i = 0; i <= leadEnd; ++i) { + Row& row = rows[i]; + row.pos = i == 0 ? layoutStartDev + row.marginLeadDev + : prevPos + prevSize + gapDev(i - 1, i); + + prevPos = row.pos; + prevSize = row.size; + } + + if (fillIdx + 1 < int(rows.size())) { + // Measured back from the layout's end edge, by the last child's own + // rounded trailing margin. Only when the engine says the children need + // more room than the layout has - an over-constrained layout, which + // overflows in Qt too - is the engine's own edge used instead. + const qint64 engineEnd = engineTrailing(rows.back()); + qint64 far = engineEnd > layoutEndDev + ? engineEnd + : layoutEndDev - rows.back().marginTrailDev; + for (int i = int(rows.size()) - 1; i > fillIdx; --i) { + Row& row = rows[i]; + row.pos = far - row.size; + far = row.pos - gapDev(i - 1, i); + } + + // Whatever the leading block left over belongs to the filling child. + Row& fill = rows[fillIdx]; + const qint64 remainder = far - fill.pos; + if (remainder >= 0) fill.size = remainder; + } else if (fillIdx < int(rows.size())) { + // A fill that ends the layout takes the whole space up to its end edge. + Row& fill = rows[fillIdx]; + const qint64 remainder = layoutEndDev - fill.pos; + if (remainder >= 0) fill.size = remainder; + } + + for (Row& row : rows) { + QQuickItem* child = row.child; + + // Nothing may be written past the layout's own end edge: a child sized + // from a rounded-up implicit size can reach past it while the layout is + // still being sized. Corrections are made in steps of two device pixels, + // so an item that centers its own content (Layout.HCenter / + // Layout.VCenter, anchors.centerIn) stays on whole device pixels. The + // layout's edge is the only ceiling - the engine's cell edge is not, + // because sizes here are rounded up and a child that asks for half a + // device pixel more than its cell is not shrunk: shrinking it would push + // the spacing behind it off the normalized value. + const qint64 mainLimit = (std::max)(layoutEndDev - row.pos, qint64(0)); + qint64 mainSize = row.size; + if (mainSize > mainLimit) { + mainSize = mainLimit; + // Keep the parity of the size the child asks for. + if (int(mainSize % 2) != int(row.size % 2)) --mainSize; + } + mainSize = (std::max)(mainSize, qint64(0)); + + const qint64 near = sgn > 0 ? row.pos : -row.pos - mainSize; + const qint64 width = horiz ? mainSize : row.cross; + const qint64 height = horiz ? row.cross : mainSize; + + // What is remembered is what the child asked for, not what was written: + // a size shortened by the ceiling above must be tried again once the + // layout has room, or the content stays shorter than the layout's own + // implicit size forever - a Flickable that takes that implicit size as + // its content height then has bounds that do not match its content. d->childSizes[child] = SnapLayoutPrivate::ChildSize{ - engineWidth, engineHeight, width, height}; + row.engineWidth, + row.engineHeight, + horiz ? row.size : row.cross, + horiz ? row.cross : row.size}; - prevEndExact = mainExact + mainSizeExact; - prevNearDev = mainNear; - prevSizeDev = horiz ? width : height; - - const qint64 crossNear = std::llround(crossExact); - const qint64 nearX = originXDev + (horiz ? mainNear : crossNear); - const qint64 nearY = originYDev + (horiz ? crossNear : mainNear); + const qint64 nearX = originXDev + (horiz ? near : row.crossExact); + const qint64 nearY = originYDev + (horiz ? row.crossExact : near); const QRectF snapped( qreal(nearX) / dpr - origin.x(), diff --git a/Plugins/ZShell/Config/CMakeLists.txt b/Plugins/ZShell/Config/CMakeLists.txt index 5b11d2f..216966e 100644 --- a/Plugins/ZShell/Config/CMakeLists.txt +++ b/Plugins/ZShell/Config/CMakeLists.txt @@ -30,6 +30,8 @@ qml_module(ZShell-config utilities.hpp DEPENDENCIES ZShell.Settings + LIBRARIES + Qt::Quick ) target_link_libraries(ZShell-config PUBLIC diff --git a/Plugins/ZShell/Config/appearance.hpp b/Plugins/ZShell/Config/appearance.hpp index b774969..ad48619 100644 --- a/Plugins/ZShell/Config/appearance.hpp +++ b/Plugins/ZShell/Config/appearance.hpp @@ -134,25 +134,11 @@ struct FontConfig { } // 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) \ + class FontConfig##Style##Size : public settings::ObjectNode { \ + CONFIG_NODE(FontConfig##Style##Size, settings::ObjectNode) \ \ CONFIG_PROPERTY( \ QString, \ @@ -168,18 +154,13 @@ class FontStyleNode : public settings::ObjectNode { FONT_CONFIG(Style, Medium, ARG(medium_)) \ FONT_CONFIG(Style, Small, ARG(small_)) \ \ - class FontStyle##Style : public FontStyleNode { \ - CONFIG_NODE(FontStyle##Style, FontStyleNode) \ + class FontStyle##Style : public settings::ObjectNode { \ + CONFIG_NODE(FontStyle##Style, settings::ObjectNode) \ \ 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 @@ -215,20 +196,14 @@ FONT_CONFIG(Icon, Large, FONT({.size = static_cast(32 / 1.33)})) FONT_CONFIG(Icon, Medium, FONT({.size = static_cast(24 / 1.33)})) FONT_CONFIG(Icon, Small, FONT({.size = static_cast(20 / 1.33)})) -class FontStyleIcon : public FontStyleNode { - CONFIG_NODE(FontStyleIcon, FontStyleNode) +class FontStyleIcon : public settings::ObjectNode { + CONFIG_NODE(FontStyleIcon, settings::ObjectNode) 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 diff --git a/Plugins/ZShell/Config/font.cpp b/Plugins/ZShell/Config/font.cpp index 8e89cab..0365d0d 100644 --- a/Plugins/ZShell/Config/font.cpp +++ b/Plugins/ZShell/Config/font.cpp @@ -5,6 +5,15 @@ namespace ZShell::config { +namespace { + +settings::ObjectNode* style( + const settings::ObjectNode* cfg, const QString& key) { + return cfg->value(key).value(); +} + +} // namespace + FontStyleBase::FontStyleBase(QObject* parent) : QObject(parent) {} QFont FontStyleBase::large() const { @@ -20,12 +29,14 @@ QFont FontStyleBase::small() const { } 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(); - const auto italic = cfg->property("italic").toBool(); - const auto vaxes = cfg->property("vaxes").toMap(); + const settings::ObjectNode* cfg, + const QString& fallbackFamily, + qreal scale) { + const auto family = cfg->value(u"family"_s).toString(); + const auto size = cfg->value(u"size"_s).toInt(); + const auto weight = cfg->value(u"weight"_s).value(); + const auto italic = cfg->value(u"italic"_s).toBool(); + const auto vaxes = cfg->value(u"vaxes"_s).toMap(); QFont font; font.setFamily(family.isEmpty() ? fallbackFamily : family); @@ -45,14 +56,14 @@ QFont FontStyleBase::buildFont( return font; } -void FontStyleBase::bind(FontStyleNode* cfg) { +void FontStyleBase::bind(settings::ObjectNode* 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); + disconnect(style(m_cfg, u"large"_s), nullptr, this, nullptr); + disconnect(style(m_cfg, u"medium"_s), nullptr, this, nullptr); + disconnect(style(m_cfg, u"small"_s), nullptr, this, nullptr); } m_cfg = cfg; @@ -61,17 +72,17 @@ void FontStyleBase::bind(FontStyleNode* cfg) { connect( cfg, &settings::Node::optionChanged, this, &FontStyleBase::rebuild); connect( - cfg->largeNode(), + style(cfg, u"large"_s), &settings::Node::optionChanged, this, &FontStyleBase::rebuild); connect( - cfg->mediumNode(), + style(cfg, u"medium"_s), &settings::Node::optionChanged, this, &FontStyleBase::rebuild); connect( - cfg->smallNode(), + style(cfg, u"small"_s), &settings::Node::optionChanged, this, &FontStyleBase::rebuild); @@ -82,10 +93,10 @@ void FontStyleBase::bind(FontStyleNode* cfg) { 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); + const auto family = m_cfg->value(u"family"_s).toString(); + m_large = buildFont(style(m_cfg, u"large"_s), family, m_scale); + m_medium = buildFont(style(m_cfg, u"medium"_s), family, m_scale); + m_small = buildFont(style(m_cfg, u"small"_s), family, m_scale); } else { m_large = QFont(); m_medium = QFont(); @@ -118,16 +129,17 @@ FontBuilder IconFontStyle::pixelSize(int pixelSize) { return FontBuilder(m_small).pixelSize(pixelSize); } -void IconFontStyle::bind(FontStyleNode* cfg) { +void IconFontStyle::bind(settings::ObjectNode* cfg) { if (m_cfg == cfg) return; - if (m_cfg) disconnect(m_cfg->extraLargeNode(), nullptr, this, nullptr); + if (m_cfg) + disconnect(style(m_cfg, u"extraLarge"_s), nullptr, this, nullptr); FontStyleBase::bind(cfg); if (cfg) connect( - cfg->extraLargeNode(), + style(cfg, u"extraLarge"_s), &settings::Node::optionChanged, this, &IconFontStyle::rebuild); @@ -143,8 +155,9 @@ IconFontBuilders* IconFontStyle::builders() const { void IconFontStyle::rebuild() { if (m_cfg) { - const auto family = m_cfg->property("family").toString(); - m_extraLarge = buildFont(m_cfg->extraLargeNode(), family, m_scale); + const auto family = m_cfg->value(u"family"_s).toString(); + m_extraLarge = + buildFont(style(m_cfg, u"extraLarge"_s), family, m_scale); } else { m_extraLarge = QFont(); } diff --git a/Plugins/ZShell/Config/font.hpp b/Plugins/ZShell/Config/font.hpp index 5e61794..9c106b1 100644 --- a/Plugins/ZShell/Config/font.hpp +++ b/Plugins/ZShell/Config/font.hpp @@ -2,18 +2,16 @@ #include "fontbuilder.hpp" -#include #include #include #include +#include "settings/objectnode.hpp" + namespace ZShell::config { class AppearanceFont; -class FontBuilders; class FontStyleBase; -class FontSizeNode; -class FontStyleNode; class IconFontStyle; class FontBuilders : public QObject { @@ -69,7 +67,7 @@ class FontStyleBase : public QObject { public: explicit FontStyleBase(QObject* parent = nullptr); - virtual void bind(FontStyleNode* cfg); + virtual void bind(settings::ObjectNode* cfg); void setScale(qreal scale); [[nodiscard]] QFont large() const; @@ -83,9 +81,11 @@ class FontStyleBase : public QObject { virtual void rebuild(); static QFont buildFont( - const FontSizeNode* cfg, const QString& fallbackFamily, qreal scale); + const settings::ObjectNode* cfg, + const QString& fallbackFamily, + qreal scale); - FontStyleNode* m_cfg = nullptr; + settings::ObjectNode* m_cfg = nullptr; qreal m_scale = 1; QFont m_large; QFont m_medium; @@ -96,7 +96,8 @@ class FontStyle : public FontStyleBase { Q_OBJECT QML_ANONYMOUS - Q_PROPERTY(FontBuilders* builders READ builders CONSTANT FINAL) + Q_PROPERTY( + ZShell::config::FontBuilders* builders READ builders CONSTANT FINAL) public: explicit FontStyle(QObject* parent = nullptr); @@ -112,15 +113,16 @@ class IconFontStyle : public FontStyleBase { QML_ANONYMOUS Q_PROPERTY(QFont extraLarge READ extraLarge NOTIFY fontsChanged FINAL) - Q_PROPERTY(IconFontBuilders* builders READ builders CONSTANT FINAL) + Q_PROPERTY( + ZShell::config::IconFontBuilders* builders READ builders CONSTANT FINAL) public: explicit IconFontStyle(QObject* parent = nullptr); - Q_INVOKABLE FontBuilder size(int pointSize); - Q_INVOKABLE FontBuilder pixelSize(int pixelSize); + Q_INVOKABLE ZShell::config::FontBuilder size(int pointSize); + Q_INVOKABLE ZShell::config::FontBuilder pixelSize(int pixelSize); - void bind(FontStyleNode* cfg) override; + void bind(settings::ObjectNode* cfg) override; [[nodiscard]] QFont extraLarge() const; [[nodiscard]] IconFontBuilders* builders() const;