fix: font macro expansion + snaplayout jitter with fillWidth: true
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import Quickshell
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
@@ -101,7 +100,7 @@ WidgetBase {
|
|||||||
Layout.bottomMargin: Tokens.padding.extraSmall ?? 2
|
Layout.bottomMargin: Tokens.padding.extraSmall ?? 2
|
||||||
Layout.topMargin: Tokens.padding.extraSmall ?? 2
|
Layout.topMargin: Tokens.padding.extraSmall ?? 2
|
||||||
color: root.contentColor
|
color: root.contentColor
|
||||||
height: 1
|
implicitHeight: 1
|
||||||
implicitWidth: root.baseSize * 0.5
|
implicitWidth: root.baseSize * 0.5
|
||||||
opacity: 0.4
|
opacity: 0.4
|
||||||
radius: 1
|
radius: 1
|
||||||
@@ -123,7 +122,7 @@ WidgetBase {
|
|||||||
if (modelData.includes("m"))
|
if (modelData.includes("m"))
|
||||||
return Time.minuteStr;
|
return Time.minuteStr;
|
||||||
if (modelData.includes("s"))
|
if (modelData.includes("s"))
|
||||||
return Time.secondStr;
|
return Time.secondStr.split(" ")[0];
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -172,6 +172,10 @@ QSizeF SnapLayout::sizeHint(Qt::SizeHint whichSizeHint) const {
|
|||||||
if (dpr <= 0.0) return engineHint;
|
if (dpr <= 0.0) return engineHint;
|
||||||
|
|
||||||
const bool horiz = d->orientation == Qt::Horizontal;
|
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:
|
// Qt builds implicit sizes out of whole logical pixels:
|
||||||
// QQuickLayout::effectiveSizeHints_helper takes `qCeil(item->implicitHeight())`
|
// 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.
|
// Rounding up keeps the hint from ever under-sizing its contents.
|
||||||
const qint64 mainDev = qint64(std::ceil(mainHint * dpr - 1e-9));
|
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 =
|
const qint64 crossDevForChild =
|
||||||
qint64(std::ceil(crossHint * dpr - 1e-9)) +
|
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;
|
mainExtentDev += mainDev + mainMarginDev;
|
||||||
maxMainDev = (std::max)(maxMainDev, 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 originXDev = std::llround(origin.x() * dpr);
|
||||||
const qint64 originYDev = std::llround(origin.y() * 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
|
// 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
|
// a Flickable that takes this layout's height as its content height can still
|
||||||
// reach the last child.
|
// reach the last child.
|
||||||
@@ -610,6 +628,60 @@ void SnapLayout::arrangeChildren() {
|
|||||||
const qint64 layoutMainExtentDev =
|
const qint64 layoutMainExtentDev =
|
||||||
qint64(std::floor((horiz ? self.width() : self.height()) * dpr + 1e-6));
|
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<Row> rows;
|
||||||
|
rows.reserve(d->engine.itemCount());
|
||||||
|
|
||||||
for (int i = 0; i < d->engine.itemCount(); ++i) {
|
for (int i = 0; i < d->engine.itemCount(); ++i) {
|
||||||
SnapGridLayoutItem* gridItem =
|
SnapGridLayoutItem* gridItem =
|
||||||
static_cast<SnapGridLayoutItem*>(d->engine.itemAt(i));
|
static_cast<SnapGridLayoutItem*>(d->engine.itemAt(i));
|
||||||
@@ -620,129 +692,190 @@ void SnapLayout::arrangeChildren() {
|
|||||||
const qreal mainExact = (horiz ? cell.x() : cell.y()) * dpr;
|
const qreal mainExact = (horiz ? cell.x() : cell.y()) * dpr;
|
||||||
const qreal mainSizeExact =
|
const qreal mainSizeExact =
|
||||||
(horiz ? cell.width() : cell.height()) * dpr;
|
(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 mainSizeDev = std::llround(mainSizeExact);
|
||||||
const qint64 crossSizeDev =
|
const qint64 crossSizeDev =
|
||||||
std::llround((horiz ? cell.height() : cell.width()) * dpr);
|
std::llround((horiz ? cell.height() : cell.width()) * dpr);
|
||||||
const qint64 engineWidth = horiz ? mainSizeDev : crossSizeDev;
|
row.engineWidth = horiz ? mainSizeDev : crossSizeDev;
|
||||||
const qint64 engineHeight = horiz ? crossSizeDev : mainSizeDev;
|
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);
|
QQuickLayoutAttached* info = attachedLayoutObject(child, false);
|
||||||
const qreal mainOwn = horiz ? child->implicitWidth()
|
const qreal mainOwn = horiz ? child->implicitWidth()
|
||||||
: child->implicitHeight();
|
: child->implicitHeight();
|
||||||
const qreal crossOwn = horiz ? child->implicitHeight()
|
const qreal crossOwn = horiz ? child->implicitHeight()
|
||||||
: child->implicitWidth();
|
: child->implicitWidth();
|
||||||
const bool fillsMain = info &&
|
row.fillsMain = info &&
|
||||||
(horiz ? info->fillWidth() : info->fillHeight());
|
(horiz ? info->fillWidth() : info->fillHeight());
|
||||||
const bool fillsCross =
|
const bool fillsCross =
|
||||||
info && (horiz ? info->fillHeight() : info->fillWidth());
|
info && (horiz ? info->fillHeight() : info->fillWidth());
|
||||||
|
row.mainOwnDev = snappedOwn(
|
||||||
const auto snappedOwn = [&](bool fills, qreal cellSize, qreal ownSize) {
|
row.fillsMain, horiz ? cell.width() : cell.height(), mainOwn);
|
||||||
if (fills || ownSize <= 0.0 || qAbs(cellSize - ownSize) >= 1.0)
|
row.crossOwnDev = snappedOwn(
|
||||||
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(
|
|
||||||
fillsCross, horiz ? cell.height() : cell.width(), crossOwn);
|
fillsCross, horiz ? cell.height() : cell.width(), crossOwn);
|
||||||
|
|
||||||
// The size along the main axis. An implicit-sized child keeps its own
|
const QMarginsF margins = info ? info->effectiveQMargins()
|
||||||
// implicit size, which carries the device-pixel parity ZUtils chose for
|
: QMarginsF(0, 0, 0, 0);
|
||||||
// it. Rounding is not neutral: an odd logical size at 1.5x is exactly half
|
const qreal marginLead =
|
||||||
// a device pixel and always rounds up, so left unchecked every child adds
|
horiz ? (sgn > 0 ? margins.left() : margins.right())
|
||||||
// half a pixel and a long list ends outside the layout - a Flickable reads
|
: margins.top();
|
||||||
// the layout's height as the end of the content and the last rows become
|
const qreal marginTrail =
|
||||||
// unreachable. Corrections are made in steps of two device pixels: an item
|
horiz ? (sgn > 0 ? margins.right() : margins.left())
|
||||||
// that centers its own content (Layout.HCenter / Layout.VCenter,
|
: margins.bottom();
|
||||||
// anchors.centerIn) lands on whole device pixels only while its device
|
row.marginLeadDev = std::llround(marginLead * dpr);
|
||||||
// size keeps that parity, so a one-pixel correction moves its centered
|
row.marginTrailDev = std::llround(marginTrail * dpr);
|
||||||
// children onto half a pixel and they antialias. Drift from the engine's
|
rows.append(row);
|
||||||
// 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;
|
|
||||||
|
|
||||||
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 (crossOwnDev >= 0) {
|
|
||||||
if (horiz)
|
|
||||||
height = crossOwnDev;
|
|
||||||
else
|
|
||||||
width = crossOwnDev;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rows.isEmpty()) return;
|
||||||
|
|
||||||
|
// 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
|
// Keep the size already written unless the engine asks for a different
|
||||||
// one, or something else resized the child. An aligned size stays
|
// one, or something else resized the child. An aligned size stays
|
||||||
// aligned wherever the layout moves, and resizing again costs the child
|
// aligned wherever the layout moves, and resizing again costs the child
|
||||||
// a relayout: an Image reloads its texture, a nested layout rearranges.
|
// 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() &&
|
if (stored != d->childSizes.constEnd() &&
|
||||||
stored.value().engineWidth == engineWidth &&
|
stored.value().engineWidth == row.engineWidth &&
|
||||||
stored.value().engineHeight == engineHeight &&
|
stored.value().engineHeight == row.engineHeight &&
|
||||||
std::llround(child->width() * dpr) == stored.value().width &&
|
std::llround(row.child->width() * dpr) == stored.value().width &&
|
||||||
std::llround(child->height() * dpr) == stored.value().height) {
|
std::llround(row.child->height() * dpr) == stored.value().height) {
|
||||||
width = stored.value().width;
|
main = horiz ? stored.value().width : stored.value().height;
|
||||||
height = stored.value().height;
|
cross = horiz ? stored.value().height : stored.value().width;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A remembered size can predate the layout being sized, so the limit is
|
row.size = (std::max)(main, qint64(0));
|
||||||
// applied to whatever ends up being written, keeping the parity of the
|
row.cross = (std::max)(cross, qint64(0));
|
||||||
// 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));
|
|
||||||
|
|
||||||
|
// 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{
|
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;
|
const qint64 nearX = originXDev + (horiz ? near : row.crossExact);
|
||||||
prevNearDev = mainNear;
|
const qint64 nearY = originYDev + (horiz ? row.crossExact : near);
|
||||||
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 QRectF snapped(
|
const QRectF snapped(
|
||||||
qreal(nearX) / dpr - origin.x(),
|
qreal(nearX) / dpr - origin.x(),
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ qml_module(ZShell-config
|
|||||||
utilities.hpp
|
utilities.hpp
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
ZShell.Settings
|
ZShell.Settings
|
||||||
|
LIBRARIES
|
||||||
|
Qt::Quick
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(ZShell-config PUBLIC
|
target_link_libraries(ZShell-config PUBLIC
|
||||||
|
|||||||
@@ -134,25 +134,11 @@ struct FontConfig {
|
|||||||
|
|
||||||
} // namespace detail
|
} // 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 ARG(...) __VA_ARGS__
|
||||||
#define FONT(...) ZShell::config::detail::FontConfig __VA_ARGS__
|
#define FONT(...) ZShell::config::detail::FontConfig __VA_ARGS__
|
||||||
#define FONT_CONFIG(Style, Size, props) \
|
#define FONT_CONFIG(Style, Size, props) \
|
||||||
class FontConfig##Style##Size : public FontSizeNode { \
|
class FontConfig##Style##Size : public settings::ObjectNode { \
|
||||||
CONFIG_NODE(FontConfig##Style##Size, FontSizeNode) \
|
CONFIG_NODE(FontConfig##Style##Size, settings::ObjectNode) \
|
||||||
\
|
\
|
||||||
CONFIG_PROPERTY( \
|
CONFIG_PROPERTY( \
|
||||||
QString, \
|
QString, \
|
||||||
@@ -168,18 +154,13 @@ class FontStyleNode : public settings::ObjectNode {
|
|||||||
FONT_CONFIG(Style, Medium, ARG(medium_)) \
|
FONT_CONFIG(Style, Medium, ARG(medium_)) \
|
||||||
FONT_CONFIG(Style, Small, ARG(small_)) \
|
FONT_CONFIG(Style, Small, ARG(small_)) \
|
||||||
\
|
\
|
||||||
class FontStyle##Style : public FontStyleNode { \
|
class FontStyle##Style : public settings::ObjectNode { \
|
||||||
CONFIG_NODE(FontStyle##Style, FontStyleNode) \
|
CONFIG_NODE(FontStyle##Style, settings::ObjectNode) \
|
||||||
\
|
\
|
||||||
CONFIG_PROPERTY(QString, family, family_) \
|
CONFIG_PROPERTY(QString, family, family_) \
|
||||||
CONFIG_SUBOBJECT(FontConfig##Style##Large, large) \
|
CONFIG_SUBOBJECT(FontConfig##Style##Large, large) \
|
||||||
CONFIG_SUBOBJECT(FontConfig##Style##Medium, medium) \
|
CONFIG_SUBOBJECT(FontConfig##Style##Medium, medium) \
|
||||||
CONFIG_SUBOBJECT(FontConfig##Style##Small, small) \
|
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
|
// clang-format off
|
||||||
@@ -215,20 +196,14 @@ 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, Medium, FONT({.size = static_cast<int>(24 / 1.33)}))
|
||||||
FONT_CONFIG(Icon, Small, FONT({.size = static_cast<int>(20 / 1.33)}))
|
FONT_CONFIG(Icon, Small, FONT({.size = static_cast<int>(20 / 1.33)}))
|
||||||
|
|
||||||
class FontStyleIcon : public FontStyleNode {
|
class FontStyleIcon : public settings::ObjectNode {
|
||||||
CONFIG_NODE(FontStyleIcon, FontStyleNode)
|
CONFIG_NODE(FontStyleIcon, settings::ObjectNode)
|
||||||
|
|
||||||
CONFIG_PROPERTY(QString, family, u"Material Symbols Rounded"_s)
|
CONFIG_PROPERTY(QString, family, u"Material Symbols Rounded"_s)
|
||||||
CONFIG_SUBOBJECT(FontConfigIconExtraLarge, extraLarge)
|
CONFIG_SUBOBJECT(FontConfigIconExtraLarge, extraLarge)
|
||||||
CONFIG_SUBOBJECT(FontConfigIconLarge, large)
|
CONFIG_SUBOBJECT(FontConfigIconLarge, large)
|
||||||
CONFIG_SUBOBJECT(FontConfigIconMedium, medium)
|
CONFIG_SUBOBJECT(FontConfigIconMedium, medium)
|
||||||
CONFIG_SUBOBJECT(FontConfigIconSmall, small)
|
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 ARG
|
||||||
|
|||||||
@@ -5,6 +5,15 @@
|
|||||||
|
|
||||||
namespace ZShell::config {
|
namespace ZShell::config {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
settings::ObjectNode* style(
|
||||||
|
const settings::ObjectNode* cfg, const QString& key) {
|
||||||
|
return cfg->value(key).value<settings::ObjectNode*>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
FontStyleBase::FontStyleBase(QObject* parent) : QObject(parent) {}
|
FontStyleBase::FontStyleBase(QObject* parent) : QObject(parent) {}
|
||||||
|
|
||||||
QFont FontStyleBase::large() const {
|
QFont FontStyleBase::large() const {
|
||||||
@@ -20,12 +29,14 @@ QFont FontStyleBase::small() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QFont FontStyleBase::buildFont(
|
QFont FontStyleBase::buildFont(
|
||||||
const FontSizeNode* cfg, const QString& fallbackFamily, qreal scale) {
|
const settings::ObjectNode* cfg,
|
||||||
const auto family = cfg->property("family").toString();
|
const QString& fallbackFamily,
|
||||||
const auto size = cfg->property("size").toInt();
|
qreal scale) {
|
||||||
const auto weight = cfg->property("weight").value<QFont::Weight>();
|
const auto family = cfg->value(u"family"_s).toString();
|
||||||
const auto italic = cfg->property("italic").toBool();
|
const auto size = cfg->value(u"size"_s).toInt();
|
||||||
const auto vaxes = cfg->property("vaxes").toMap();
|
const auto weight = cfg->value(u"weight"_s).value<QFont::Weight>();
|
||||||
|
const auto italic = cfg->value(u"italic"_s).toBool();
|
||||||
|
const auto vaxes = cfg->value(u"vaxes"_s).toMap();
|
||||||
|
|
||||||
QFont font;
|
QFont font;
|
||||||
font.setFamily(family.isEmpty() ? fallbackFamily : family);
|
font.setFamily(family.isEmpty() ? fallbackFamily : family);
|
||||||
@@ -45,14 +56,14 @@ QFont FontStyleBase::buildFont(
|
|||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FontStyleBase::bind(FontStyleNode* cfg) {
|
void FontStyleBase::bind(settings::ObjectNode* cfg) {
|
||||||
if (m_cfg == cfg) return;
|
if (m_cfg == cfg) return;
|
||||||
|
|
||||||
if (m_cfg) {
|
if (m_cfg) {
|
||||||
disconnect(m_cfg, nullptr, this, nullptr);
|
disconnect(m_cfg, nullptr, this, nullptr);
|
||||||
disconnect(m_cfg->largeNode(), nullptr, this, nullptr);
|
disconnect(style(m_cfg, u"large"_s), nullptr, this, nullptr);
|
||||||
disconnect(m_cfg->mediumNode(), nullptr, this, nullptr);
|
disconnect(style(m_cfg, u"medium"_s), nullptr, this, nullptr);
|
||||||
disconnect(m_cfg->smallNode(), nullptr, this, nullptr);
|
disconnect(style(m_cfg, u"small"_s), nullptr, this, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_cfg = cfg;
|
m_cfg = cfg;
|
||||||
@@ -61,17 +72,17 @@ void FontStyleBase::bind(FontStyleNode* cfg) {
|
|||||||
connect(
|
connect(
|
||||||
cfg, &settings::Node::optionChanged, this, &FontStyleBase::rebuild);
|
cfg, &settings::Node::optionChanged, this, &FontStyleBase::rebuild);
|
||||||
connect(
|
connect(
|
||||||
cfg->largeNode(),
|
style(cfg, u"large"_s),
|
||||||
&settings::Node::optionChanged,
|
&settings::Node::optionChanged,
|
||||||
this,
|
this,
|
||||||
&FontStyleBase::rebuild);
|
&FontStyleBase::rebuild);
|
||||||
connect(
|
connect(
|
||||||
cfg->mediumNode(),
|
style(cfg, u"medium"_s),
|
||||||
&settings::Node::optionChanged,
|
&settings::Node::optionChanged,
|
||||||
this,
|
this,
|
||||||
&FontStyleBase::rebuild);
|
&FontStyleBase::rebuild);
|
||||||
connect(
|
connect(
|
||||||
cfg->smallNode(),
|
style(cfg, u"small"_s),
|
||||||
&settings::Node::optionChanged,
|
&settings::Node::optionChanged,
|
||||||
this,
|
this,
|
||||||
&FontStyleBase::rebuild);
|
&FontStyleBase::rebuild);
|
||||||
@@ -82,10 +93,10 @@ void FontStyleBase::bind(FontStyleNode* cfg) {
|
|||||||
|
|
||||||
void FontStyleBase::rebuild() {
|
void FontStyleBase::rebuild() {
|
||||||
if (m_cfg) {
|
if (m_cfg) {
|
||||||
const auto family = m_cfg->property("family").toString();
|
const auto family = m_cfg->value(u"family"_s).toString();
|
||||||
m_large = buildFont(m_cfg->largeNode(), family, m_scale);
|
m_large = buildFont(style(m_cfg, u"large"_s), family, m_scale);
|
||||||
m_medium = buildFont(m_cfg->mediumNode(), family, m_scale);
|
m_medium = buildFont(style(m_cfg, u"medium"_s), family, m_scale);
|
||||||
m_small = buildFont(m_cfg->smallNode(), family, m_scale);
|
m_small = buildFont(style(m_cfg, u"small"_s), family, m_scale);
|
||||||
} else {
|
} else {
|
||||||
m_large = QFont();
|
m_large = QFont();
|
||||||
m_medium = QFont();
|
m_medium = QFont();
|
||||||
@@ -118,16 +129,17 @@ FontBuilder IconFontStyle::pixelSize(int pixelSize) {
|
|||||||
return FontBuilder(m_small).pixelSize(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 == 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);
|
FontStyleBase::bind(cfg);
|
||||||
|
|
||||||
if (cfg)
|
if (cfg)
|
||||||
connect(
|
connect(
|
||||||
cfg->extraLargeNode(),
|
style(cfg, u"extraLarge"_s),
|
||||||
&settings::Node::optionChanged,
|
&settings::Node::optionChanged,
|
||||||
this,
|
this,
|
||||||
&IconFontStyle::rebuild);
|
&IconFontStyle::rebuild);
|
||||||
@@ -143,8 +155,9 @@ IconFontBuilders* IconFontStyle::builders() const {
|
|||||||
|
|
||||||
void IconFontStyle::rebuild() {
|
void IconFontStyle::rebuild() {
|
||||||
if (m_cfg) {
|
if (m_cfg) {
|
||||||
const auto family = m_cfg->property("family").toString();
|
const auto family = m_cfg->value(u"family"_s).toString();
|
||||||
m_extraLarge = buildFont(m_cfg->extraLargeNode(), family, m_scale);
|
m_extraLarge =
|
||||||
|
buildFont(style(m_cfg, u"extraLarge"_s), family, m_scale);
|
||||||
} else {
|
} else {
|
||||||
m_extraLarge = QFont();
|
m_extraLarge = QFont();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,16 @@
|
|||||||
|
|
||||||
#include "fontbuilder.hpp"
|
#include "fontbuilder.hpp"
|
||||||
|
|
||||||
#include <qfont.h>
|
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
#include <qvariantmap.h>
|
#include <qvariantmap.h>
|
||||||
|
|
||||||
|
#include "settings/objectnode.hpp"
|
||||||
|
|
||||||
namespace ZShell::config {
|
namespace ZShell::config {
|
||||||
|
|
||||||
class AppearanceFont;
|
class AppearanceFont;
|
||||||
class FontBuilders;
|
|
||||||
class FontStyleBase;
|
class FontStyleBase;
|
||||||
class FontSizeNode;
|
|
||||||
class FontStyleNode;
|
|
||||||
class IconFontStyle;
|
class IconFontStyle;
|
||||||
|
|
||||||
class FontBuilders : public QObject {
|
class FontBuilders : public QObject {
|
||||||
@@ -69,7 +67,7 @@ class FontStyleBase : public QObject {
|
|||||||
public:
|
public:
|
||||||
explicit FontStyleBase(QObject* parent = nullptr);
|
explicit FontStyleBase(QObject* parent = nullptr);
|
||||||
|
|
||||||
virtual void bind(FontStyleNode* cfg);
|
virtual void bind(settings::ObjectNode* cfg);
|
||||||
void setScale(qreal scale);
|
void setScale(qreal scale);
|
||||||
|
|
||||||
[[nodiscard]] QFont large() const;
|
[[nodiscard]] QFont large() const;
|
||||||
@@ -83,9 +81,11 @@ class FontStyleBase : public QObject {
|
|||||||
virtual void rebuild();
|
virtual void rebuild();
|
||||||
|
|
||||||
static QFont buildFont(
|
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;
|
qreal m_scale = 1;
|
||||||
QFont m_large;
|
QFont m_large;
|
||||||
QFont m_medium;
|
QFont m_medium;
|
||||||
@@ -96,7 +96,8 @@ class FontStyle : public FontStyleBase {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ANONYMOUS
|
QML_ANONYMOUS
|
||||||
|
|
||||||
Q_PROPERTY(FontBuilders* builders READ builders CONSTANT FINAL)
|
Q_PROPERTY(
|
||||||
|
ZShell::config::FontBuilders* builders READ builders CONSTANT FINAL)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FontStyle(QObject* parent = nullptr);
|
explicit FontStyle(QObject* parent = nullptr);
|
||||||
@@ -112,15 +113,16 @@ class IconFontStyle : public FontStyleBase {
|
|||||||
QML_ANONYMOUS
|
QML_ANONYMOUS
|
||||||
|
|
||||||
Q_PROPERTY(QFont extraLarge READ extraLarge NOTIFY fontsChanged FINAL)
|
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:
|
public:
|
||||||
explicit IconFontStyle(QObject* parent = nullptr);
|
explicit IconFontStyle(QObject* parent = nullptr);
|
||||||
|
|
||||||
Q_INVOKABLE FontBuilder size(int pointSize);
|
Q_INVOKABLE ZShell::config::FontBuilder size(int pointSize);
|
||||||
Q_INVOKABLE FontBuilder pixelSize(int pixelSize);
|
Q_INVOKABLE ZShell::config::FontBuilder pixelSize(int pixelSize);
|
||||||
|
|
||||||
void bind(FontStyleNode* cfg) override;
|
void bind(settings::ObjectNode* cfg) override;
|
||||||
|
|
||||||
[[nodiscard]] QFont extraLarge() const;
|
[[nodiscard]] QFont extraLarge() const;
|
||||||
[[nodiscard]] IconFontBuilders* builders() const;
|
[[nodiscard]] IconFontBuilders* builders() const;
|
||||||
|
|||||||
Reference in New Issue
Block a user