fix: graph jitter on load + format
This commit is contained in:
@@ -61,8 +61,8 @@ Item {
|
|||||||
implicitHeight: mainLayout.implicitHeight + mainLayout.anchors.margins * 2
|
implicitHeight: mainLayout.implicitHeight + mainLayout.anchors.margins * 2
|
||||||
|
|
||||||
SnapLayout {
|
SnapLayout {
|
||||||
orientation: Qt.Vertical
|
|
||||||
id: mainLayout
|
id: mainLayout
|
||||||
|
orientation: Qt.Vertical
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Tokens.padding.large
|
anchors.margins: Tokens.padding.large
|
||||||
spacing: 0
|
spacing: 0
|
||||||
@@ -102,31 +102,6 @@ orientation: Qt.Vertical
|
|||||||
line1FillAlpha: 0.15
|
line1FillAlpha: 0.15
|
||||||
maxValue: 1.0
|
maxValue: 1.0
|
||||||
slideInterval: Config.dashboard.resourceUpdateInterval
|
slideInterval: Config.dashboard.resourceUpdateInterval
|
||||||
slideProgress: 1
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onValuesChanged(): void {
|
|
||||||
slideAnim.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
target: card.buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
id: slideAnim
|
|
||||||
|
|
||||||
// Only drives frames; the slide phase is the sample
|
|
||||||
// clock inside SparklineItem. It must not stop at the
|
|
||||||
// end of a cycle, or the newest value is held on the
|
|
||||||
// clipped edge until the next sample arrives.
|
|
||||||
duration: Config.dashboard.resourceUpdateInterval
|
|
||||||
easing.type: Easing.Linear
|
|
||||||
from: 0
|
|
||||||
loops: Animation.Infinite
|
|
||||||
property: "slideProgress"
|
|
||||||
target: sparkline
|
|
||||||
to: 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Collecting data" placeholder
|
// "Collecting data" placeholder
|
||||||
@@ -139,8 +114,8 @@ orientation: Qt.Vertical
|
|||||||
}
|
}
|
||||||
|
|
||||||
SnapLayout {
|
SnapLayout {
|
||||||
orientation: Qt.Vertical
|
|
||||||
id: infoColumn
|
id: infoColumn
|
||||||
|
orientation: Qt.Vertical
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
width: {
|
width: {
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ CustomRect {
|
|||||||
line2FillAlpha: 0.2
|
line2FillAlpha: 0.2
|
||||||
maxValue: smoothMax
|
maxValue: smoothMax
|
||||||
slideInterval: Config.dashboard.resourceUpdateInterval
|
slideInterval: Config.dashboard.resourceUpdateInterval
|
||||||
slideProgress: 1
|
|
||||||
|
|
||||||
Behavior on smoothMax {
|
Behavior on smoothMax {
|
||||||
Anim {}
|
Anim {}
|
||||||
@@ -76,23 +75,10 @@ CustomRect {
|
|||||||
Connections {
|
Connections {
|
||||||
function onUpdateTriggered(): void {
|
function onUpdateTriggered(): void {
|
||||||
sparkline.targetMax = Math.max(NetworkUsage.downloadBuffer.maximum, NetworkUsage.uploadBuffer.maximum, 1024);
|
sparkline.targetMax = Math.max(NetworkUsage.downloadBuffer.maximum, NetworkUsage.uploadBuffer.maximum, 1024);
|
||||||
slideAnim.restart();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
target: NetworkUsage
|
target: NetworkUsage
|
||||||
}
|
}
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
id: slideAnim
|
|
||||||
|
|
||||||
duration: Config.dashboard.resourceUpdateInterval
|
|
||||||
easing.type: Easing.Linear
|
|
||||||
from: 0
|
|
||||||
loops: Animation.Infinite
|
|
||||||
property: "slideProgress"
|
|
||||||
target: sparkline
|
|
||||||
to: 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Collecting data" placeholder
|
// "Collecting data" placeholder
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ namespace {
|
|||||||
constexpr qreal kNaN = std::numeric_limits<qreal>::quiet_NaN();
|
constexpr qreal kNaN = std::numeric_limits<qreal>::quiet_NaN();
|
||||||
|
|
||||||
bool sameReal(qreal a, qreal b) {
|
bool sameReal(qreal a, qreal b) {
|
||||||
if (std::isnan(a) || std::isnan(b))
|
if (std::isnan(a) || std::isnan(b)) return std::isnan(a) && std::isnan(b);
|
||||||
return std::isnan(a) && std::isnan(b);
|
|
||||||
return qFuzzyCompare(a, b);
|
return qFuzzyCompare(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,8 +33,7 @@ qreal parseValue(const QVariant& v) {
|
|||||||
// Index of the segment whose right endpoint is at or past targetX.
|
// Index of the segment whose right endpoint is at or past targetX.
|
||||||
int indexAt(const QVector<QPointF>& points, qreal targetX) {
|
int indexAt(const QVector<QPointF>& points, qreal targetX) {
|
||||||
for (int i = 0; i < points.size() - 1; ++i) {
|
for (int i = 0; i < points.size() - 1; ++i) {
|
||||||
if (targetX <= points[i + 1].x())
|
if (targetX <= points[i + 1].x()) return i;
|
||||||
return i;
|
|
||||||
}
|
}
|
||||||
const int last = static_cast<int>(points.size()) - 2;
|
const int last = static_cast<int>(points.size()) - 2;
|
||||||
return last > 0 ? last : 0;
|
return last > 0 ? last : 0;
|
||||||
@@ -44,13 +42,9 @@ int indexAt(const QVector<QPointF>& points, qreal targetX) {
|
|||||||
// The part of the polyline left/right of targetX, ending or beginning exactly at
|
// The part of the polyline left/right of targetX, ending or beginning exactly at
|
||||||
// the curve point there. Used to carve a gap around the marker dot.
|
// the curve point there. Used to carve a gap around the marker dot.
|
||||||
QVector<QPointF> cutLeft(
|
QVector<QPointF> cutLeft(
|
||||||
const QVector<QPointF>& points,
|
const QVector<QPointF>& points, qreal targetX, bool curveSmoothing) {
|
||||||
qreal targetX,
|
if (targetX <= points.first().x()) return {};
|
||||||
bool curveSmoothing) {
|
if (targetX >= points.last().x()) return points;
|
||||||
if (targetX <= points.first().x())
|
|
||||||
return {};
|
|
||||||
if (targetX >= points.last().x())
|
|
||||||
return points;
|
|
||||||
|
|
||||||
const int j = indexAt(points, targetX);
|
const int j = indexAt(points, targetX);
|
||||||
QVector<QPointF> left = points.first(j + 1);
|
QVector<QPointF> left = points.first(j + 1);
|
||||||
@@ -60,13 +54,9 @@ QVector<QPointF> cutLeft(
|
|||||||
}
|
}
|
||||||
|
|
||||||
QVector<QPointF> cutRight(
|
QVector<QPointF> cutRight(
|
||||||
const QVector<QPointF>& points,
|
const QVector<QPointF>& points, qreal targetX, bool curveSmoothing) {
|
||||||
qreal targetX,
|
if (targetX >= points.last().x()) return {};
|
||||||
bool curveSmoothing) {
|
if (targetX <= points.first().x()) return points;
|
||||||
if (targetX >= points.last().x())
|
|
||||||
return {};
|
|
||||||
if (targetX <= points.first().x())
|
|
||||||
return points;
|
|
||||||
|
|
||||||
const int j = indexAt(points, targetX);
|
const int j = indexAt(points, targetX);
|
||||||
QVector<QPointF> right = points.mid(j + 1);
|
QVector<QPointF> right = points.mid(j + 1);
|
||||||
@@ -76,27 +66,20 @@ QVector<QPointF> cutRight(
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString formatXLabel(LineGraph::XLabelMode mode, int i, bool use12) {
|
QString formatXLabel(LineGraph::XLabelMode mode, int i, bool use12) {
|
||||||
if (mode == LineGraph::XLabelMode::None)
|
if (mode == LineGraph::XLabelMode::None) return {};
|
||||||
return {};
|
if (mode == LineGraph::XLabelMode::Number) return QString::number(i);
|
||||||
if (mode == LineGraph::XLabelMode::Number)
|
|
||||||
return QString::number(i);
|
|
||||||
// Hour: an hour of day 0..24, on a 12- or 24-hour clock.
|
// Hour: an hour of day 0..24, on a 12- or 24-hour clock.
|
||||||
if (use12) {
|
if (use12) {
|
||||||
const int hr = (i % 12 == 0) ? 12 : (i % 12);
|
const int hr = (i % 12 == 0) ? 12 : (i % 12);
|
||||||
return QString::number(hr)
|
return QString::number(hr) +
|
||||||
+ (i < 12 ? QStringLiteral(" AM")
|
(i < 12 ? QStringLiteral(" AM") : QStringLiteral(" PM"));
|
||||||
: QStringLiteral(" PM"));
|
|
||||||
}
|
}
|
||||||
return QString::number(i);
|
return QString::number(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString formatYLabel(
|
QString formatYLabel(
|
||||||
LineGraph::YLabelMode mode,
|
LineGraph::YLabelMode mode, qreal v, int decimals, const QString& suffix) {
|
||||||
qreal v,
|
if (mode == LineGraph::YLabelMode::Off) return {};
|
||||||
int decimals,
|
|
||||||
const QString& suffix) {
|
|
||||||
if (mode == LineGraph::YLabelMode::Off)
|
|
||||||
return {};
|
|
||||||
return QString::number(v, 'f', qMax(0, decimals)) + suffix;
|
return QString::number(v, 'f', qMax(0, decimals)) + suffix;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -194,7 +177,7 @@ void LineGraphRenderer::buildModel(const LineGraph* graph) {
|
|||||||
|
|
||||||
const qreal padLeft = graph->m_labelFontSize * 5.0;
|
const qreal padLeft = graph->m_labelFontSize * 5.0;
|
||||||
const qreal padBottom = graph->m_labelFontSize + 14.0;
|
const qreal padBottom = graph->m_labelFontSize + 14.0;
|
||||||
model.plot = Plot {padLeft, 0.0, W - padLeft, H - padBottom};
|
model.plot = Plot{padLeft, 0.0, W - padLeft, H - padBottom};
|
||||||
if (model.plot.w < 2 || model.plot.h < 2) {
|
if (model.plot.w < 2 || model.plot.h < 2) {
|
||||||
m_model = model;
|
m_model = model;
|
||||||
return;
|
return;
|
||||||
@@ -222,8 +205,7 @@ void LineGraphRenderer::buildModel(const LineGraph* graph) {
|
|||||||
int n = 0;
|
int n = 0;
|
||||||
for (int j = -radius; j <= radius; ++j) {
|
for (int j = -radius; j <= radius; ++j) {
|
||||||
const int u = i + j;
|
const int u = i + j;
|
||||||
if (u < 0 || u >= raw.size())
|
if (u < 0 || u >= raw.size()) continue;
|
||||||
continue;
|
|
||||||
if (!std::isnan(raw[u])) {
|
if (!std::isnan(raw[u])) {
|
||||||
sum += raw[u];
|
sum += raw[u];
|
||||||
++n;
|
++n;
|
||||||
@@ -242,8 +224,7 @@ void LineGraphRenderer::buildModel(const LineGraph* graph) {
|
|||||||
qreal dmin = std::numeric_limits<qreal>::infinity();
|
qreal dmin = std::numeric_limits<qreal>::infinity();
|
||||||
qreal dmax = -std::numeric_limits<qreal>::infinity();
|
qreal dmax = -std::numeric_limits<qreal>::infinity();
|
||||||
for (const qreal v : series) {
|
for (const qreal v : series) {
|
||||||
if (std::isnan(v))
|
if (std::isnan(v)) continue;
|
||||||
continue;
|
|
||||||
dmin = std::min(dmin, v);
|
dmin = std::min(dmin, v);
|
||||||
dmax = std::max(dmax, v);
|
dmax = std::max(dmax, v);
|
||||||
}
|
}
|
||||||
@@ -258,8 +239,8 @@ void LineGraphRenderer::buildModel(const LineGraph* graph) {
|
|||||||
: 0;
|
: 0;
|
||||||
if (step > 0) {
|
if (step > 0) {
|
||||||
if (hiAuto) {
|
if (hiAuto) {
|
||||||
const qreal baseLo =
|
const qreal baseLo = loAuto ? std::floor(dmin / step) * step
|
||||||
loAuto ? std::floor(dmin / step) * step : lo;
|
: lo;
|
||||||
const int span = std::max(
|
const int span = std::max(
|
||||||
{static_cast<int>(std::ceil((dmax - baseLo) / step)),
|
{static_cast<int>(std::ceil((dmax - baseLo) / step)),
|
||||||
minSteps,
|
minSteps,
|
||||||
@@ -274,12 +255,9 @@ void LineGraphRenderer::buildModel(const LineGraph* graph) {
|
|||||||
lo = hi - qreal(span) * step;
|
lo = hi - qreal(span) * step;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (loAuto)
|
if (loAuto) lo = dmin;
|
||||||
lo = dmin;
|
if (hiAuto) hi = dmax;
|
||||||
if (hiAuto)
|
if (hi <= lo) hi = lo + 1.0;
|
||||||
hi = dmax;
|
|
||||||
if (hi <= lo)
|
|
||||||
hi = lo + 1.0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,8 +273,7 @@ void LineGraphRenderer::buildModel(const LineGraph* graph) {
|
|||||||
return model.plot.x + (i / qreal(graph->m_xSteps)) * model.plot.w;
|
return model.plot.x + (i / qreal(graph->m_xSteps)) * model.plot.w;
|
||||||
};
|
};
|
||||||
const auto yAt = [&](qreal v) {
|
const auto yAt = [&](qreal v) {
|
||||||
return model.plot.y
|
return model.plot.y + (1.0 - (v - scaleBottom) / range) * model.plot.h;
|
||||||
+ (1.0 - (v - scaleBottom) / range) * model.plot.h;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto devW = [&](qreal w) {
|
const auto devW = [&](qreal w) {
|
||||||
@@ -310,53 +287,48 @@ void LineGraphRenderer::buildModel(const LineGraph* graph) {
|
|||||||
const int vStep = graph->m_verticalLineStep;
|
const int vStep = graph->m_verticalLineStep;
|
||||||
const int hStep = graph->m_horizontalLineStep;
|
const int hStep = graph->m_horizontalLineStep;
|
||||||
const auto tickVisible = [&](int i) {
|
const auto tickVisible = [&](int i) {
|
||||||
if (i == 0 && !graph->m_firstXLine)
|
if (i == 0 && !graph->m_firstXLine) return false;
|
||||||
return false;
|
if (i == graph->m_xSteps && !graph->m_lastXLine) return false;
|
||||||
if (i == graph->m_xSteps && !graph->m_lastXLine)
|
|
||||||
return false;
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (vStep > 0) {
|
if (vStep > 0) {
|
||||||
for (int i = 0; i <= graph->m_xSteps; i += vStep) {
|
for (int i = 0; i <= graph->m_xSteps; i += vStep) {
|
||||||
if (!tickVisible(i))
|
if (!tickVisible(i)) continue;
|
||||||
continue;
|
model.vGrid.append(internal::snapLine(xAt(i), gridWDev, dpr));
|
||||||
model.vGrid.append(
|
|
||||||
internal::snapLine(xAt(i), gridWDev, dpr));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const qreal vStart = lo
|
const qreal vStart =
|
||||||
- (graph->m_yPaddingBottom > 0.0
|
lo - (graph->m_yPaddingBottom > 0.0
|
||||||
? std::floor(graph->m_yPaddingBottom / hStep)
|
? std::floor(graph->m_yPaddingBottom / hStep) * hStep
|
||||||
* hStep
|
|
||||||
: 0.0);
|
: 0.0);
|
||||||
if (hStep > 0) {
|
if (hStep > 0) {
|
||||||
for (qreal v = vStart; v <= hi + 0.001; v += hStep)
|
for (qreal v = vStart; v <= hi + 0.001; v += hStep)
|
||||||
model.hGrid.append(
|
model.hGrid.append(internal::snapLine(yAt(v), gridWDev, dpr));
|
||||||
internal::snapLine(yAt(v), gridWDev, dpr));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vStep > 0 && graph->m_xLabelMode != LineGraph::XLabelMode::None) {
|
if (vStep > 0 && graph->m_xLabelMode != LineGraph::XLabelMode::None) {
|
||||||
for (int i = 0; i <= graph->m_xSteps; i += vStep) {
|
for (int i = 0; i <= graph->m_xSteps; i += vStep) {
|
||||||
if (!tickVisible(i))
|
if (!tickVisible(i)) continue;
|
||||||
continue;
|
|
||||||
const QString text = formatXLabel(
|
const QString text = formatXLabel(
|
||||||
graph->m_xLabelMode, i, graph->m_useTwelveHourClock);
|
graph->m_xLabelMode, i, graph->m_useTwelveHourClock);
|
||||||
if (!text.isEmpty())
|
if (!text.isEmpty())
|
||||||
model.xLabels.append(Label {
|
model.xLabels.append(
|
||||||
text, xAt(i) + 4.0,
|
Label{
|
||||||
model.plot.y + model.plot.h + 8.0});
|
text, xAt(i) + 4.0, model.plot.y + model.plot.h + 8.0});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hStep > 0 && graph->m_yLabelMode != LineGraph::YLabelMode::Off) {
|
if (hStep > 0 && graph->m_yLabelMode != LineGraph::YLabelMode::Off) {
|
||||||
for (qreal v = vStart; v <= hi + 0.001; v += hStep) {
|
for (qreal v = vStart; v <= hi + 0.001; v += hStep) {
|
||||||
const QString text = formatYLabel(
|
const QString text = formatYLabel(
|
||||||
graph->m_yLabelMode, v, graph->m_yLabelDecimals,
|
graph->m_yLabelMode,
|
||||||
|
v,
|
||||||
|
graph->m_yLabelDecimals,
|
||||||
graph->m_yLabelSuffix);
|
graph->m_yLabelSuffix);
|
||||||
if (!text.isEmpty())
|
if (!text.isEmpty())
|
||||||
model.yLabels.append(Label {text, 2.0, yAt(v) - 2.0});
|
model.yLabels.append(Label{text, 2.0, yAt(v) - 2.0});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,36 +350,33 @@ void LineGraphRenderer::buildModel(const LineGraph* graph) {
|
|||||||
}
|
}
|
||||||
current.append(QPointF(snapX(xAt(i)), yAt(series[i])));
|
current.append(QPointF(snapX(xAt(i)), yAt(series[i])));
|
||||||
}
|
}
|
||||||
if (!current.isEmpty())
|
if (!current.isEmpty()) runs.append(current);
|
||||||
runs.append(current);
|
|
||||||
|
|
||||||
const bool curve = graph->m_curveSmoothing;
|
const bool curve = graph->m_curveSmoothing;
|
||||||
const bool hasMarker =
|
const bool hasMarker = graph->m_markerX >= 0.0 &&
|
||||||
graph->m_markerX >= 0.0 && graph->m_markerX <= graph->m_xSteps;
|
graph->m_markerX <= graph->m_xSteps;
|
||||||
qreal markerPx = 0.0;
|
qreal markerPx = 0.0;
|
||||||
qreal gapPx = 0.0;
|
qreal gapPx = 0.0;
|
||||||
if (hasMarker) {
|
if (hasMarker) {
|
||||||
markerPx = xAt(graph->m_markerX);
|
markerPx = xAt(graph->m_markerX);
|
||||||
qreal slope = 0.0;
|
qreal slope = 0.0;
|
||||||
for (const auto& run : runs) {
|
for (const auto& run : runs) {
|
||||||
if (run.size() > 2 && run.first().x() <= markerPx
|
if (run.size() > 2 && run.first().x() <= markerPx &&
|
||||||
&& markerPx <= run.last().x()
|
markerPx <= run.last().x() &&
|
||||||
&& markerPx - 1.0 >= run.first().x()
|
markerPx - 1.0 >= run.first().x() &&
|
||||||
&& markerPx + 1.0 <= run.last().x()) {
|
markerPx + 1.0 <= run.last().x()) {
|
||||||
slope = (internal::pointAtX(run, markerPx + 1.0, curve).y()
|
slope = (internal::pointAtX(run, markerPx + 1.0, curve).y() -
|
||||||
- internal::pointAtX(run, markerPx - 1.0, curve)
|
internal::pointAtX(run, markerPx - 1.0, curve).y()) /
|
||||||
.y())
|
2.0;
|
||||||
/ 2.0;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gapPx = (graph->m_markerDotRadius + graph->m_markerGap)
|
gapPx = (graph->m_markerDotRadius + graph->m_markerGap) /
|
||||||
/ std::sqrt(1.0 + slope * slope);
|
std::sqrt(1.0 + slope * slope);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& run : runs) {
|
for (const auto& run : runs) {
|
||||||
if (run.size() < 2)
|
if (run.size() < 2) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
Segment seg;
|
Segment seg;
|
||||||
seg.fill = internal::curveVertices(run, dpr, curve);
|
seg.fill = internal::curveVertices(run, dpr, curve);
|
||||||
@@ -415,16 +384,13 @@ void LineGraphRenderer::buildModel(const LineGraph* graph) {
|
|||||||
seg.fill.append(QPointF(run.first().x(), model.plotBottom));
|
seg.fill.append(QPointF(run.first().x(), model.plotBottom));
|
||||||
|
|
||||||
if (hasMarker) {
|
if (hasMarker) {
|
||||||
const QVector<QPointF> left =
|
const QVector<QPointF> left = cutLeft(run, markerPx - gapPx, curve);
|
||||||
cutLeft(run, markerPx - gapPx, curve);
|
|
||||||
const QVector<QPointF> right =
|
const QVector<QPointF> right =
|
||||||
cutRight(run, markerPx + gapPx, curve);
|
cutRight(run, markerPx + gapPx, curve);
|
||||||
if (left.size() >= 2)
|
if (left.size() >= 2)
|
||||||
seg.strokes.append(
|
seg.strokes.append(internal::curveVertices(left, dpr, curve));
|
||||||
internal::curveVertices(left, dpr, curve));
|
|
||||||
if (right.size() >= 2)
|
if (right.size() >= 2)
|
||||||
seg.strokes.append(
|
seg.strokes.append(internal::curveVertices(right, dpr, curve));
|
||||||
internal::curveVertices(right, dpr, curve));
|
|
||||||
} else {
|
} else {
|
||||||
seg.strokes.append(internal::curveVertices(run, dpr, curve));
|
seg.strokes.append(internal::curveVertices(run, dpr, curve));
|
||||||
}
|
}
|
||||||
@@ -433,8 +399,7 @@ void LineGraphRenderer::buildModel(const LineGraph* graph) {
|
|||||||
|
|
||||||
if (hasMarker) {
|
if (hasMarker) {
|
||||||
model.marker.hasLine = true;
|
model.marker.hasLine = true;
|
||||||
model.marker.lineX =
|
model.marker.lineX = internal::snapLine(markerPx, gridWDev, dpr);
|
||||||
internal::snapLine(markerPx, gridWDev, dpr);
|
|
||||||
|
|
||||||
// Dashes are drawn as solid segments: QCanvasPainter strokes are always
|
// Dashes are drawn as solid segments: QCanvasPainter strokes are always
|
||||||
// solid, so a dashed line is a run of short strokes, as LineGraph.qml did.
|
// solid, so a dashed line is a run of short strokes, as LineGraph.qml did.
|
||||||
@@ -442,19 +407,15 @@ void LineGraphRenderer::buildModel(const LineGraph* graph) {
|
|||||||
bool on = true;
|
bool on = true;
|
||||||
while (dashY < H) {
|
while (dashY < H) {
|
||||||
const qreal dashEnd = std::min(dashY + 4.0, H);
|
const qreal dashEnd = std::min(dashY + 4.0, H);
|
||||||
if (on)
|
if (on) model.marker.dash.append({snapX(dashY), snapX(dashEnd)});
|
||||||
model.marker.dash.append(
|
|
||||||
{snapX(dashY), snapX(dashEnd)});
|
|
||||||
dashY = dashEnd;
|
dashY = dashEnd;
|
||||||
on = !on;
|
on = !on;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int i0 = static_cast<int>(std::floor(graph->m_markerX));
|
const int i0 = static_cast<int>(std::floor(graph->m_markerX));
|
||||||
const qreal v0 = (i0 >= 0 && i0 < series.size()) ? series[i0]
|
const qreal v0 = (i0 >= 0 && i0 < series.size()) ? series[i0] : kNaN;
|
||||||
: kNaN;
|
const qreal v1 =
|
||||||
const qreal v1 = (i0 + 1 >= 0 && i0 + 1 < series.size())
|
(i0 + 1 >= 0 && i0 + 1 < series.size()) ? series[i0 + 1] : kNaN;
|
||||||
? series[i0 + 1]
|
|
||||||
: kNaN;
|
|
||||||
qreal y = kNaN;
|
qreal y = kNaN;
|
||||||
if (!std::isnan(v0) && !std::isnan(v1))
|
if (!std::isnan(v0) && !std::isnan(v1))
|
||||||
y = yAt(v0 + (v1 - v0) * (graph->m_markerX - qreal(i0)));
|
y = yAt(v0 + (v1 - v0) * (graph->m_markerX - qreal(i0)));
|
||||||
@@ -475,8 +436,7 @@ void LineGraphRenderer::buildModel(const LineGraph* graph) {
|
|||||||
|
|
||||||
void LineGraphRenderer::paint(QCanvasPainter* painter) {
|
void LineGraphRenderer::paint(QCanvasPainter* painter) {
|
||||||
painter->clearRect(0, 0, width(), height());
|
painter->clearRect(0, 0, width(), height());
|
||||||
if (!m_model.valid)
|
if (!m_model.valid) return;
|
||||||
return;
|
|
||||||
|
|
||||||
const Model& m = m_model;
|
const Model& m = m_model;
|
||||||
|
|
||||||
@@ -553,8 +513,7 @@ void LineGraphRenderer::paint(QCanvasPainter* painter) {
|
|||||||
if (m.marker.hasDot) {
|
if (m.marker.hasDot) {
|
||||||
painter->setFillStyle(m_markerColor);
|
painter->setFillStyle(m_markerColor);
|
||||||
painter->beginPath();
|
painter->beginPath();
|
||||||
painter->circle(
|
painter->circle(m.marker.dot.x(), m.marker.dot.y(), m.marker.dotRadius);
|
||||||
m.marker.dot.x(), m.marker.dot.y(), m.marker.dotRadius);
|
|
||||||
painter->fill();
|
painter->fill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ class LineGraph : public QCanvasPainterItem {
|
|||||||
bool firstXLine READ firstXLine WRITE setFirstXLine NOTIFY
|
bool firstXLine READ firstXLine WRITE setFirstXLine NOTIFY
|
||||||
firstXLineChanged FINAL)
|
firstXLineChanged FINAL)
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(
|
||||||
bool lastXLine READ lastXLine WRITE setLastXLine NOTIFY
|
bool lastXLine READ lastXLine WRITE setLastXLine NOTIFY lastXLineChanged
|
||||||
lastXLineChanged FINAL)
|
FINAL)
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(
|
||||||
int minHorizontalLines READ minHorizontalLines WRITE
|
int minHorizontalLines READ minHorizontalLines WRITE
|
||||||
setMinHorizontalLines NOTIFY minHorizontalLinesChanged FINAL)
|
setMinHorizontalLines NOTIFY minHorizontalLinesChanged FINAL)
|
||||||
|
|||||||
@@ -16,43 +16,21 @@ using internal::fromDev;
|
|||||||
using internal::snapLine;
|
using internal::snapLine;
|
||||||
using internal::toDev;
|
using internal::toDev;
|
||||||
|
|
||||||
// Device-pixel snapping, bezier sampling and Catmull-Rom controls come from
|
|
||||||
// internal::linegeometry, shared with LineGraph so both graphs align and smooth
|
|
||||||
// their lines identically.
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// A QQuickPaintedItem only lands on the device grid when its logical size is a
|
|
||||||
// whole number *and* that whole number times dpr is a whole number as well.
|
|
||||||
// Qt derives the painter node from qRound(size) (qquickpainteditem.cpp), makes
|
|
||||||
// the texture from that rounded size times dpr, scales the painter by
|
|
||||||
// texture/size rather than by dpr, and draws the texture into a quad of the
|
|
||||||
// rounded size (qsgdefaultpainternode.cpp). Any fraction therefore moves the
|
|
||||||
// painter scale off dpr - a height of 75.3333 at dpr 1.5 becomes a node of 75
|
|
||||||
// and a texture of qRound(112.5) = 113, a scale of 1.5067, so every y snapped
|
|
||||||
// to the dpr 1.5 grid falls between texture rows and the whole texture is then
|
|
||||||
// resampled to a 112.5 pixel tall quad. That is the fuzz. 74 -> 111 and
|
|
||||||
// 76 -> 114 are both exact.
|
|
||||||
//
|
|
||||||
// `grow` picks the direction: shrink to stay inside the slot, grow to stay
|
|
||||||
// outside it. The overhang on the right has to grow, or the texture's own edge
|
|
||||||
// moves inside the parent's clip and slides left and right with the scroll.
|
|
||||||
qreal snapPaintedExtent(qreal logical, qreal dpr, bool grow) {
|
qreal snapPaintedExtent(qreal logical, qreal dpr, bool grow) {
|
||||||
qreal whole = grow ? std::ceil(logical - 1e-6) : std::floor(logical + 1e-6);
|
qreal whole = grow ? std::ceil(logical - 1e-6) : std::floor(logical + 1e-6);
|
||||||
for (int guard = 0; guard < 16 && whole >= 1.0; ++guard) {
|
for (int guard = 0; guard < 16 && whole >= 1.0; ++guard) {
|
||||||
const qreal device = whole * dpr;
|
const qreal device = whole * dpr;
|
||||||
if (std::abs(device - std::round(device)) < 1e-6)
|
if (std::abs(device - std::round(device)) < 1e-6) return whole;
|
||||||
return whole;
|
|
||||||
whole += grow ? 1.0 : -1.0;
|
whole += grow ? 1.0 : -1.0;
|
||||||
}
|
}
|
||||||
// No whole extent within the guard window (an exotic dpr): a whole number is
|
|
||||||
// still closer than a fractional one, which Qt rounds away anyway.
|
|
||||||
return std::max(1.0, grow ? whole : std::floor(logical));
|
return std::max(1.0, grow ? whole : std::floor(logical));
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
SparklineContent::SparklineContent(QQuickItem* parent)
|
SparklineContent::SparklineContent(QQuickItem* parent)
|
||||||
: QQuickPaintedItem(parent) {
|
: QQuickPaintedItem(parent) {
|
||||||
m_sampleClock.start();
|
|
||||||
setAntialiasing(true);
|
setAntialiasing(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,13 +53,6 @@ qreal SparklineContent::stepX() const {
|
|||||||
2,
|
2,
|
||||||
(m_line1 && m_line1->count() >= 2) ? m_line1->count()
|
(m_line1 && m_line1->count() >= 2) ? m_line1->count()
|
||||||
: (m_line2 ? m_line2->count() : 2));
|
: (m_line2 ? m_line2->count() : 2));
|
||||||
// Exactly the slot divided by the history, in logical pixels. Rounding this
|
|
||||||
// to a whole device pixel was tempting - it puts every sample on a pixel
|
|
||||||
// column - but the rounding error accumulates over the whole window: history
|
|
||||||
// - 1 steps then no longer span the slot, so the oldest value either leaves
|
|
||||||
// the clip before its tick (rounding up) or sits at the edge until the buffer
|
|
||||||
// evicts it, popping out at the tick (rounding down). crispness does not need
|
|
||||||
// it: internal::curveVertices puts a vertex on every device column anyway.
|
|
||||||
return m_sliding ? m_viewportW / static_cast<qreal>(m_historyLength - 1)
|
return m_sliding ? m_viewportW / static_cast<qreal>(m_historyLength - 1)
|
||||||
: m_viewportW / static_cast<qreal>(len - 1);
|
: m_viewportW / static_cast<qreal>(len - 1);
|
||||||
}
|
}
|
||||||
@@ -99,23 +70,23 @@ void SparklineContent::setViewportSize(
|
|||||||
void SparklineContent::setSlideInterval(qreal intervalMs) {
|
void SparklineContent::setSlideInterval(qreal intervalMs) {
|
||||||
if (qFuzzyCompare(m_slideIntervalMs, intervalMs)) return;
|
if (qFuzzyCompare(m_slideIntervalMs, intervalMs)) return;
|
||||||
m_slideIntervalMs = intervalMs;
|
m_slideIntervalMs = intervalMs;
|
||||||
m_sampleClock.restart();
|
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal SparklineContent::slidePhase(qreal progress) const {
|
qreal SparklineContent::slidePhase(qreal progress) const {
|
||||||
if (m_slideIntervalMs <= 0.0) return qBound(0.0, progress, 1.0);
|
if (m_slideIntervalMs <= 0.0) return qBound(0.0, progress, 1.0);
|
||||||
// The sample clock, not the animation's timeline. The NumberAnimation runs on
|
|
||||||
// the render-loop clock while the sample arrives from a QTimer of the same
|
if (!m_sampleClock.isValid()) return 1.0;
|
||||||
// duration, so it reaches 1 on the last frame before that timer fires and then
|
|
||||||
// stops. For that frame the newest value is parked exactly on the parent's
|
|
||||||
// clip edge - it shows up at the clipped edge instead of sliding in from
|
|
||||||
// beyond it, and the following frame travels nothing. Holding is capped at one
|
|
||||||
// step, so a stalled producer cannot scroll the curve into empty space.
|
|
||||||
const qreal elapsed = static_cast<qreal>(m_sampleClock.elapsed());
|
const qreal elapsed = static_cast<qreal>(m_sampleClock.elapsed());
|
||||||
return qBound(0.0, elapsed / m_slideIntervalMs, 1.0);
|
return qBound(0.0, elapsed / m_slideIntervalMs, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SparklineContent::slideActive() const {
|
||||||
|
return m_slideIntervalMs > 0.0 && m_sampleClock.isValid() &&
|
||||||
|
static_cast<qreal>(m_sampleClock.elapsed()) < m_slideIntervalMs;
|
||||||
|
}
|
||||||
|
|
||||||
void SparklineContent::setSlideProgress(qreal progress) {
|
void SparklineContent::setSlideProgress(qreal progress) {
|
||||||
setX(-stepX() * slidePhase(progress));
|
setX(-stepX() * slidePhase(progress));
|
||||||
}
|
}
|
||||||
@@ -139,11 +110,6 @@ void SparklineContent::setLine2(internal::CircularBuffer* buffer) {
|
|||||||
void SparklineContent::onSampleArrived() {
|
void SparklineContent::onSampleArrived() {
|
||||||
m_sampleClock.restart();
|
m_sampleClock.restart();
|
||||||
invalidate();
|
invalidate();
|
||||||
// The offset reset and the buffer shifting its values by one step are a
|
|
||||||
// single change: applied here, both are pending before the next sync, so they
|
|
||||||
// cannot land in different frames. Left to the animation to write, a frame
|
|
||||||
// can paint the reindexed values with the previous cycle's full offset still
|
|
||||||
// in place, which moves the whole graph one extra step left for that frame.
|
|
||||||
setSlideProgress(0.0);
|
setSlideProgress(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,16 +189,7 @@ void SparklineContent::invalidate() {
|
|||||||
|
|
||||||
void SparklineContent::applyGeometry() {
|
void SparklineContent::applyGeometry() {
|
||||||
const qreal dpr = m_dpr > 0.0 ? m_dpr : 1.0;
|
const qreal dpr = m_dpr > 0.0 ? m_dpr : 1.0;
|
||||||
// The newest value is drawn one step past the slot's right edge, so the child
|
|
||||||
// has to overhang the slot and the overhang is offset left as it slides. Two
|
|
||||||
// steps, not one: the newest vertex sits at one step past the edge and its
|
|
||||||
// stroke cap extends beyond that, so a one-step overhang puts the texture's
|
|
||||||
// own right edge exactly on the clip boundary at the end of a cycle, where the
|
|
||||||
// cut-off ink becomes visible for a frame.
|
|
||||||
const qreal pad = m_sliding ? 2.0 * stepX() : 0.0;
|
const qreal pad = m_sliding ? 2.0 * stepX() : 0.0;
|
||||||
// Qt rounds the painted size anyway, so choose a whole value that keeps
|
|
||||||
// device alignment. Width grows, to keep that overhang outside the clip;
|
|
||||||
// height shrinks, to stay inside the slot.
|
|
||||||
const qreal paintedW = snapPaintedExtent(m_viewportW + pad, dpr, true);
|
const qreal paintedW = snapPaintedExtent(m_viewportW + pad, dpr, true);
|
||||||
m_paintedH = snapPaintedExtent(m_viewportH, dpr, false);
|
m_paintedH = snapPaintedExtent(m_viewportH, dpr, false);
|
||||||
setSize(QSizeF(paintedW, m_paintedH));
|
setSize(QSizeF(paintedW, m_paintedH));
|
||||||
@@ -241,13 +198,10 @@ void SparklineContent::applyGeometry() {
|
|||||||
SparklineContent::LineGeometry SparklineContent::buildGeometry(
|
SparklineContent::LineGeometry SparklineContent::buildGeometry(
|
||||||
internal::CircularBuffer* buffer) const {
|
internal::CircularBuffer* buffer) const {
|
||||||
LineGeometry geom;
|
LineGeometry geom;
|
||||||
// Every input has to be a usable number: a NaN from QML (an animated maxValue
|
|
||||||
// fed by an empty buffer, for instance) would otherwise reach llround and the
|
|
||||||
// path stroker as garbage, which draws a shape that ignores the curve.
|
|
||||||
if (!buffer || buffer->count() < 2 || !std::isfinite(m_maxValue) ||
|
if (!buffer || buffer->count() < 2 || !std::isfinite(m_maxValue) ||
|
||||||
m_maxValue <= 0.0 || !std::isfinite(m_viewportW) ||
|
m_maxValue <= 0.0 || !std::isfinite(m_viewportW) ||
|
||||||
m_viewportW <= 0.0 || !std::isfinite(m_viewportH) || m_viewportH <= 0.0 ||
|
m_viewportW <= 0.0 || !std::isfinite(m_viewportH) ||
|
||||||
m_paintedH < 1.0)
|
m_viewportH <= 0.0 || m_paintedH < 1.0)
|
||||||
return geom;
|
return geom;
|
||||||
if (m_historyLength < 2) return geom;
|
if (m_historyLength < 2) return geom;
|
||||||
|
|
||||||
@@ -259,8 +213,6 @@ SparklineContent::LineGeometry SparklineContent::buildGeometry(
|
|||||||
const qint64 lineDev = std::isfinite(m_lineWidth)
|
const qint64 lineDev = std::isfinite(m_lineWidth)
|
||||||
? (std::max)(qint64(1), toDev(m_lineWidth, dpr))
|
? (std::max)(qint64(1), toDev(m_lineWidth, dpr))
|
||||||
: qint64(1);
|
: qint64(1);
|
||||||
// The painted height, not the slot's: it is the number of texture rows that
|
|
||||||
// actually exist, so the plot and the fill bottom match them exactly.
|
|
||||||
const qint64 heightDev = toDev(m_paintedH, dpr);
|
const qint64 heightDev = toDev(m_paintedH, dpr);
|
||||||
|
|
||||||
qreal startX = 0.0;
|
qreal startX = 0.0;
|
||||||
@@ -269,9 +221,6 @@ SparklineContent::LineGeometry SparklineContent::buildGeometry(
|
|||||||
else
|
else
|
||||||
startX = 0.0;
|
startX = 0.0;
|
||||||
|
|
||||||
// Plot bounds in device pixels: half a stroke plus the curve overshoot at
|
|
||||||
// the top, a half stroke and one pixel of antialiasing feather at the
|
|
||||||
// bottom, so neither edge of the stroke is cut by the item's own rect.
|
|
||||||
const qint64 curvePadDev = toDev(m_curveSmoothing ? 3.0 : 0.0, dpr);
|
const qint64 curvePadDev = toDev(m_curveSmoothing ? 3.0 : 0.0, dpr);
|
||||||
const qint64 plotTopDev = lineDev / 2 + curvePadDev;
|
const qint64 plotTopDev = lineDev / 2 + curvePadDev;
|
||||||
const qint64 plotBottomDev =
|
const qint64 plotBottomDev =
|
||||||
@@ -285,26 +234,18 @@ SparklineContent::LineGeometry SparklineContent::buildGeometry(
|
|||||||
for (int i = 0; i < len; ++i) {
|
for (int i = 0; i < len; ++i) {
|
||||||
const qreal raw = buffer->smoothedAt(i);
|
const qreal raw = buffer->smoothedAt(i);
|
||||||
|
|
||||||
// A non-finite sample poisons the whole path: llround of it is undefined,
|
|
||||||
// and the stroke and fill then ignore everything after that vertex. Treat
|
|
||||||
// it as no traffic instead.
|
|
||||||
const qreal value =
|
const qreal value =
|
||||||
std::isfinite(raw) ? qBound<qreal>(0.0, raw, m_maxValue) : 0.0;
|
std::isfinite(raw) ? qBound<qreal>(0.0, raw, m_maxValue) : 0.0;
|
||||||
|
|
||||||
// The y of every sample goes on the grid the stroke width needs; x stays
|
|
||||||
// exact so the window's span is exactly the slot's width.
|
|
||||||
const qreal y = fromDev(
|
const qreal y = fromDev(
|
||||||
plotTopDev + static_cast<qint64>(std::llround(
|
plotTopDev + static_cast<qint64>(std::llround(
|
||||||
(1.0 - value / m_maxValue) *
|
(1.0 - value / m_maxValue) *
|
||||||
static_cast<double>(plotHDev))),
|
static_cast<double>(plotHDev))),
|
||||||
dpr);
|
dpr);
|
||||||
points.append(QPointF(startX + qreal(i) * step, snapLine(y, lineDev, dpr)));
|
points.append(
|
||||||
|
QPointF(startX + qreal(i) * step, snapLine(y, lineDev, dpr)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// One vertex per device-pixel column, shared with LineGraphItem through
|
|
||||||
// internal::curveVertices: a segment spanning several columns would otherwise
|
|
||||||
// interpolate its edge across all of them. y stays continuous, so a slope is
|
|
||||||
// interpolated rather than stepped one pixel at a time.
|
|
||||||
const QVector<QPointF> verts =
|
const QVector<QPointF> verts =
|
||||||
internal::curveVertices(points, dpr, m_curveSmoothing);
|
internal::curveVertices(points, dpr, m_curveSmoothing);
|
||||||
|
|
||||||
|
|||||||
@@ -31,20 +31,15 @@ class SparklineContent : public QQuickPaintedItem {
|
|||||||
|
|
||||||
void setViewportSize(qreal viewportWidth, qreal viewportHeight);
|
void setViewportSize(qreal viewportWidth, qreal viewportHeight);
|
||||||
|
|
||||||
// How often a new sample arrives, in milliseconds. With this set the slide
|
|
||||||
// phase is read from the sample clock rather than from `progress`, which
|
|
||||||
// makes the wrap exact; without it the phase is the value QML supplies.
|
|
||||||
void setSlideInterval(qreal intervalMs);
|
void setSlideInterval(qreal intervalMs);
|
||||||
|
|
||||||
// Offsets the item for the slide. Kept here rather than in SparklineItem
|
|
||||||
// because the quantization needs both stepX() and the window's dpr.
|
|
||||||
void setSlideProgress(qreal progress);
|
void setSlideProgress(qreal progress);
|
||||||
|
|
||||||
[[nodiscard]] qreal stepX() const;
|
[[nodiscard]] qreal stepX() const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool slideActive() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// The stroke width, sample spacing and plot bounds are quantized in device
|
|
||||||
// pixels, so they have to be built with the ratio the window actually has.
|
|
||||||
void itemChange(ItemChange change, const ItemChangeData& value) override;
|
void itemChange(ItemChange change, const ItemChangeData& value) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -78,13 +73,9 @@ class SparklineContent : public QQuickPaintedItem {
|
|||||||
|
|
||||||
qreal m_viewportW = 0.0;
|
qreal m_viewportW = 0.0;
|
||||||
qreal m_viewportH = 0.0;
|
qreal m_viewportH = 0.0;
|
||||||
// The height actually painted, snapped so the texture maps 1:1 onto device
|
|
||||||
// rows. The plot bounds are built from this, not from the slot's height.
|
|
||||||
qreal m_paintedH = 0.0;
|
qreal m_paintedH = 0.0;
|
||||||
qreal m_dpr = 1.0;
|
qreal m_dpr = 1.0;
|
||||||
qreal m_slideIntervalMs = 0.0;
|
qreal m_slideIntervalMs = 0.0;
|
||||||
// Restarted whenever a sample arrives, so the slide phase follows the sample
|
|
||||||
// cadence instead of the animation's timeline.
|
|
||||||
QElapsedTimer m_sampleClock;
|
QElapsedTimer m_sampleClock;
|
||||||
|
|
||||||
LineGeometry m_geom1;
|
LineGeometry m_geom1;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include "sparklinecontent.hpp"
|
#include "sparklinecontent.hpp"
|
||||||
|
|
||||||
|
#include <qquickwindow.h>
|
||||||
|
|
||||||
namespace ZShell::components {
|
namespace ZShell::components {
|
||||||
|
|
||||||
SparklineItem::SparklineItem(QQuickItem* parent) : QQuickItem(parent) {
|
SparklineItem::SparklineItem(QQuickItem* parent) : QQuickItem(parent) {
|
||||||
@@ -18,6 +20,29 @@ void SparklineItem::geometryChange(
|
|||||||
updateContentOffset();
|
updateContentOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SparklineItem::itemChange(ItemChange change, const ItemChangeData& value) {
|
||||||
|
QQuickItem::itemChange(change, value);
|
||||||
|
if (change != ItemSceneChange) return;
|
||||||
|
|
||||||
|
if (m_syncConnection) QObject::disconnect(m_syncConnection);
|
||||||
|
|
||||||
|
if (value.window) {
|
||||||
|
m_syncConnection = connect(
|
||||||
|
value.window,
|
||||||
|
&QQuickWindow::beforeFrameBegin,
|
||||||
|
this,
|
||||||
|
&SparklineItem::syncSlidePhase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SparklineItem::syncSlidePhase() {
|
||||||
|
if (m_slideInterval <= 0.0) return;
|
||||||
|
|
||||||
|
updateContentOffset();
|
||||||
|
|
||||||
|
if (isVisible() && m_content->slideActive() && window()) window()->update();
|
||||||
|
}
|
||||||
|
|
||||||
void SparklineItem::updateContentOffset() {
|
void SparklineItem::updateContentOffset() {
|
||||||
m_content->setSlideProgress(m_slideProgress);
|
m_content->setSlideProgress(m_slideProgress);
|
||||||
}
|
}
|
||||||
@@ -111,9 +136,6 @@ qreal SparklineItem::slideProgress() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SparklineItem::setSlideProgress(qreal progress) {
|
void SparklineItem::setSlideProgress(qreal progress) {
|
||||||
// Always forwarded: with a slide interval set the phase comes from the sample
|
|
||||||
// clock, so the offset can move even when the animation value repeats. An
|
|
||||||
// unchanged position is a no-op in QQuickItem.
|
|
||||||
m_slideProgress = progress;
|
m_slideProgress = progress;
|
||||||
updateContentOffset();
|
updateContentOffset();
|
||||||
emit slideProgressChanged();
|
emit slideProgressChanged();
|
||||||
|
|||||||
@@ -121,11 +121,15 @@ class SparklineItem : public QQuickItem {
|
|||||||
void geometryChange(
|
void geometryChange(
|
||||||
const QRectF& newGeometry, const QRectF& oldGeometry) override;
|
const QRectF& newGeometry, const QRectF& oldGeometry) override;
|
||||||
|
|
||||||
|
void itemChange(ItemChange change, const ItemChangeData& value) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateContentOffset();
|
void updateContentOffset();
|
||||||
|
void syncSlidePhase();
|
||||||
void applySmoothingRadius(internal::CircularBuffer* buffer) const;
|
void applySmoothingRadius(internal::CircularBuffer* buffer) const;
|
||||||
|
|
||||||
SparklineContent* m_content = nullptr;
|
SparklineContent* m_content = nullptr;
|
||||||
|
QMetaObject::Connection m_syncConnection;
|
||||||
internal::CircularBuffer* m_line1 = nullptr;
|
internal::CircularBuffer* m_line1 = nullptr;
|
||||||
internal::CircularBuffer* m_line2 = nullptr;
|
internal::CircularBuffer* m_line2 = nullptr;
|
||||||
QColor m_line1Color;
|
QColor m_line1Color;
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ qreal fromDev(qint64 device, qreal dpr) {
|
|||||||
// points depending on floating point noise.
|
// points depending on floating point noise.
|
||||||
qreal snapCenterDev(qint64 sizeDev, qint64 widthDev) {
|
qreal snapCenterDev(qint64 sizeDev, qint64 widthDev) {
|
||||||
const double halfParity = (widthDev % 2) == 0 ? 0.0 : 0.5;
|
const double halfParity = (widthDev % 2) == 0 ? 0.0 : 0.5;
|
||||||
return std::floor(static_cast<double>(sizeDev) / 2.0 - halfParity)
|
return std::floor(static_cast<double>(sizeDev) / 2.0 - halfParity) +
|
||||||
+ halfParity;
|
halfParity;
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 snapAmplitude(qint64 widthDev, qreal multiplier) {
|
qint64 snapAmplitude(qint64 widthDev, qreal multiplier) {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ namespace ZShell::internal {
|
|||||||
|
|
||||||
qreal snapLine(qreal logical, qint64 widthDev, qreal dpr) {
|
qreal snapLine(qreal logical, qint64 widthDev, qreal dpr) {
|
||||||
const qreal half = qreal(widthDev) / 2.0;
|
const qreal half = qreal(widthDev) / 2.0;
|
||||||
return (static_cast<qreal>(std::llround(logical * dpr - half)) + half)
|
return (static_cast<qreal>(std::llround(logical * dpr - half)) + half) /
|
||||||
/ dpr;
|
dpr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void segmentControls(
|
void segmentControls(
|
||||||
@@ -51,8 +51,8 @@ qreal cubicY(
|
|||||||
for (int it = 0; it < 16; ++it) {
|
for (int it = 0; it < 16; ++it) {
|
||||||
const qreal t = (lo + hi) / 2.0;
|
const qreal t = (lo + hi) / 2.0;
|
||||||
const qreal u = 1.0 - t;
|
const qreal u = 1.0 - t;
|
||||||
const qreal x = u * u * u * p1.x() + 3 * u * u * t * c1.x()
|
const qreal x = u * u * u * p1.x() + 3 * u * u * t * c1.x() +
|
||||||
+ 3 * u * t * t * c2.x() + t * t * t * p2.x();
|
3 * u * t * t * c2.x() + t * t * t * p2.x();
|
||||||
if (x < targetX)
|
if (x < targetX)
|
||||||
lo = t;
|
lo = t;
|
||||||
else
|
else
|
||||||
@@ -61,8 +61,8 @@ qreal cubicY(
|
|||||||
|
|
||||||
const qreal t = (lo + hi) / 2.0;
|
const qreal t = (lo + hi) / 2.0;
|
||||||
const qreal u = 1.0 - t;
|
const qreal u = 1.0 - t;
|
||||||
return u * u * u * p1.y() + 3 * u * u * t * c1.y()
|
return u * u * u * p1.y() + 3 * u * u * t * c1.y() +
|
||||||
+ 3 * u * t * t * c2.y() + t * t * t * p2.y();
|
3 * u * t * t * c2.y() + t * t * t * p2.y();
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -72,17 +72,14 @@ namespace {
|
|||||||
int segmentIndexAt(const QVector<QPointF>& points, qreal targetX) {
|
int segmentIndexAt(const QVector<QPointF>& points, qreal targetX) {
|
||||||
const int last = points.size() - 2;
|
const int last = points.size() - 2;
|
||||||
for (int i = 0; i < points.size() - 1; ++i) {
|
for (int i = 0; i < points.size() - 1; ++i) {
|
||||||
if (targetX <= points[i + 1].x())
|
if (targetX <= points[i + 1].x()) return i;
|
||||||
return i;
|
|
||||||
}
|
}
|
||||||
return qMax(0, last);
|
return qMax(0, last);
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
QPointF pointAtX(
|
QPointF pointAtX(
|
||||||
const QVector<QPointF>& points,
|
const QVector<QPointF>& points, qreal targetX, bool curveSmoothing) {
|
||||||
qreal targetX,
|
|
||||||
bool curveSmoothing) {
|
|
||||||
const int j = segmentIndexAt(points, targetX);
|
const int j = segmentIndexAt(points, targetX);
|
||||||
QPointF c1;
|
QPointF c1;
|
||||||
QPointF c2;
|
QPointF c2;
|
||||||
@@ -93,9 +90,8 @@ QPointF pointAtX(
|
|||||||
for (int it = 0; it < 24; ++it) {
|
for (int it = 0; it < 24; ++it) {
|
||||||
const qreal t = (lo + hi) / 2.0;
|
const qreal t = (lo + hi) / 2.0;
|
||||||
const qreal u = 1.0 - t;
|
const qreal u = 1.0 - t;
|
||||||
const qreal x = u * u * u * points[j].x()
|
const qreal x = u * u * u * points[j].x() + 3 * u * u * t * c1.x() +
|
||||||
+ 3 * u * u * t * c1.x() + 3 * u * t * t * c2.x()
|
3 * u * t * t * c2.x() + t * t * t * points[j + 1].x();
|
||||||
+ t * t * t * points[j + 1].x();
|
|
||||||
if (x < targetX)
|
if (x < targetX)
|
||||||
lo = t;
|
lo = t;
|
||||||
else
|
else
|
||||||
@@ -104,18 +100,15 @@ QPointF pointAtX(
|
|||||||
|
|
||||||
const qreal t = (lo + hi) / 2.0;
|
const qreal t = (lo + hi) / 2.0;
|
||||||
const qreal u = 1.0 - t;
|
const qreal u = 1.0 - t;
|
||||||
const qreal y = u * u * u * points[j].y() + 3 * u * u * t * c1.y()
|
const qreal y = u * u * u * points[j].y() + 3 * u * u * t * c1.y() +
|
||||||
+ 3 * u * t * t * c2.y() + t * t * t * points[j + 1].y();
|
3 * u * t * t * c2.y() + t * t * t * points[j + 1].y();
|
||||||
return QPointF(targetX, y);
|
return QPointF(targetX, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<QPointF> curveVertices(
|
QVector<QPointF> curveVertices(
|
||||||
const QVector<QPointF>& points,
|
const QVector<QPointF>& points, qreal dpr, bool curveSmoothing) {
|
||||||
qreal dpr,
|
|
||||||
bool curveSmoothing) {
|
|
||||||
QVector<QPointF> out;
|
QVector<QPointF> out;
|
||||||
if (points.size() < 2)
|
if (points.size() < 2) return out;
|
||||||
return out;
|
|
||||||
|
|
||||||
out.reserve(points.size());
|
out.reserve(points.size());
|
||||||
out.append(points.first());
|
out.append(points.first());
|
||||||
|
|||||||
@@ -49,16 +49,12 @@ qreal cubicY(
|
|||||||
// The curve point at targetX on the polyline, interpolating across the segment
|
// The curve point at targetX on the polyline, interpolating across the segment
|
||||||
// that contains it. Assumes points ascend in x.
|
// that contains it. Assumes points ascend in x.
|
||||||
QPointF pointAtX(
|
QPointF pointAtX(
|
||||||
const QVector<QPointF>& points,
|
const QVector<QPointF>& points, qreal targetX, bool curveSmoothing);
|
||||||
qreal targetX,
|
|
||||||
bool curveSmoothing);
|
|
||||||
|
|
||||||
// A dense polyline through points, adding one vertex per device-pixel column
|
// A dense polyline through points, adding one vertex per device-pixel column
|
||||||
// inside each segment so an edge never interpolates across several columns. The
|
// inside each segment so an edge never interpolates across several columns. The
|
||||||
// result starts at points.first() and ends at points.last(). points must ascend in
|
// result starts at points.first() and ends at points.last(). points must ascend in
|
||||||
// x and hold at least two entries. Backends consume it as a moveTo/lineTo chain.
|
// x and hold at least two entries. Backends consume it as a moveTo/lineTo chain.
|
||||||
QVector<QPointF> curveVertices(
|
QVector<QPointF> curveVertices(
|
||||||
const QVector<QPointF>& points,
|
const QVector<QPointF>& points, qreal dpr, bool curveSmoothing);
|
||||||
qreal dpr,
|
|
||||||
bool curveSmoothing);
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::internal
|
||||||
|
|||||||
+35
-41
@@ -383,7 +383,8 @@ Centering detectCentering(QQuickItem* item) {
|
|||||||
}
|
}
|
||||||
if (ad->usedAnchors & QQuickAnchors::VCenterAnchor) {
|
if (ad->usedAnchors & QQuickAnchors::VCenterAnchor) {
|
||||||
c.vertical = true;
|
c.vertical = true;
|
||||||
c.source += (c.source.isEmpty() ? "" : " ") + QStringLiteral("vCenter");
|
c.source +=
|
||||||
|
(c.source.isEmpty() ? "" : " ") + QStringLiteral("vCenter");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,9 +411,9 @@ Centering detectCentering(QQuickItem* item) {
|
|||||||
|
|
||||||
// Parity (mod 2) of an item's whole-device-pixel size; no item = even.
|
// Parity (mod 2) of an item's whole-device-pixel size; no item = even.
|
||||||
int devParity(const QQuickItem* it, qreal dpr, bool horizontal) {
|
int devParity(const QQuickItem* it, qreal dpr, bool horizontal) {
|
||||||
if (!it)
|
if (!it) return 0;
|
||||||
return 0;
|
const qint64 dev =
|
||||||
const qint64 dev = std::llround((horizontal ? it->width() : it->height()) * dpr);
|
std::llround((horizontal ? it->width() : it->height()) * dpr);
|
||||||
return static_cast<int>(dev % 2);
|
return static_cast<int>(dev % 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,8 +437,7 @@ qreal snapToParity(qreal value, qreal dpr, int parity, int mode) {
|
|||||||
n = (exact >= static_cast<double>(n)) ? n + 1 : n - 1;
|
n = (exact >= static_cast<double>(n)) ? n + 1 : n - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (n < 0)
|
if (n < 0) n = 0;
|
||||||
n = 0;
|
|
||||||
return static_cast<qreal>(n) / dpr;
|
return static_cast<qreal>(n) / dpr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,11 +452,12 @@ qint64 roundAway(double value) {
|
|||||||
// divides exactly), so with the parent's half at a whole device offset the
|
// divides exactly), so with the parent's half at a whole device offset the
|
||||||
// child's device size must be even; if the parent's half sits on a half device
|
// child's device size must be even; if the parent's half sits on a half device
|
||||||
// pixel, the child's device size must be odd instead.
|
// pixel, the child's device size must be odd instead.
|
||||||
qreal snapCenteredSize(qreal value, qreal dpr, qint64 parentHalfRounded, int mode) {
|
qreal snapCenteredSize(
|
||||||
|
qreal value, qreal dpr, qint64 parentHalfRounded, int mode) {
|
||||||
const double exact = value * dpr;
|
const double exact = value * dpr;
|
||||||
const double anchorDev = double(parentHalfRounded) * dpr;
|
const double anchorDev = double(parentHalfRounded) * dpr;
|
||||||
const bool anchorWhole =
|
const bool anchorWhole = std::fabs(anchorDev - std::round(anchorDev)) <
|
||||||
std::fabs(anchorDev - std::round(anchorDev)) < 1e-6;
|
1e-6;
|
||||||
const qint64 parity = anchorWhole ? 0 : 1;
|
const qint64 parity = anchorWhole ? 0 : 1;
|
||||||
|
|
||||||
qint64 base = mode > 0 ? std::llround(std::ceil(exact - 1e-6))
|
qint64 base = mode > 0 ? std::llround(std::ceil(exact - 1e-6))
|
||||||
@@ -483,7 +484,8 @@ qreal snapCenteredAxis(
|
|||||||
int mode) {
|
int mode) {
|
||||||
QQuickItem* parent = item->parentItem();
|
QQuickItem* parent = item->parentItem();
|
||||||
if (!centering.alignWhenCentered && parent) {
|
if (!centering.alignWhenCentered && parent) {
|
||||||
const qreal parentSize = horizontal ? parent->width() : parent->height();
|
const qreal parentSize = horizontal ? parent->width()
|
||||||
|
: parent->height();
|
||||||
return snapCenteredSize(value, dpr, roundAway(parentSize / 2.0), mode);
|
return snapCenteredSize(value, dpr, roundAway(parentSize / 2.0), mode);
|
||||||
}
|
}
|
||||||
return snapToParity(value, dpr, devParity(parent, dpr, horizontal), mode);
|
return snapToParity(value, dpr, devParity(parent, dpr, horizontal), mode);
|
||||||
@@ -495,10 +497,8 @@ qreal ZUtils::snap(qreal value, QQuickItem* item) {
|
|||||||
const qreal dpr = dprFor(item);
|
const qreal dpr = dprFor(item);
|
||||||
if (item) {
|
if (item) {
|
||||||
const Centering c = detectCentering(item);
|
const Centering c = detectCentering(item);
|
||||||
if (c.horizontal)
|
if (c.horizontal) return snapCenteredAxis(value, item, dpr, true, c, 0);
|
||||||
return snapCenteredAxis(value, item, dpr, true, c, 0);
|
if (c.vertical) return snapCenteredAxis(value, item, dpr, false, c, 0);
|
||||||
if (c.vertical)
|
|
||||||
return snapCenteredAxis(value, item, dpr, false, c, 0);
|
|
||||||
}
|
}
|
||||||
return std::round(value * dpr) / dpr;
|
return std::round(value * dpr) / dpr;
|
||||||
}
|
}
|
||||||
@@ -507,10 +507,8 @@ qreal ZUtils::snapCeil(qreal value, QQuickItem* item) {
|
|||||||
const qreal dpr = dprFor(item);
|
const qreal dpr = dprFor(item);
|
||||||
if (item) {
|
if (item) {
|
||||||
const Centering c = detectCentering(item);
|
const Centering c = detectCentering(item);
|
||||||
if (c.horizontal)
|
if (c.horizontal) return snapCenteredAxis(value, item, dpr, true, c, 1);
|
||||||
return snapCenteredAxis(value, item, dpr, true, c, 1);
|
if (c.vertical) return snapCenteredAxis(value, item, dpr, false, c, 1);
|
||||||
if (c.vertical)
|
|
||||||
return snapCenteredAxis(value, item, dpr, false, c, 1);
|
|
||||||
}
|
}
|
||||||
return std::ceil(value * dpr - 1e-6) / dpr;
|
return std::ceil(value * dpr - 1e-6) / dpr;
|
||||||
}
|
}
|
||||||
@@ -521,8 +519,7 @@ qreal ZUtils::snapFloor(qreal value, QQuickItem* item) {
|
|||||||
const Centering c = detectCentering(item);
|
const Centering c = detectCentering(item);
|
||||||
if (c.horizontal)
|
if (c.horizontal)
|
||||||
return snapCenteredAxis(value, item, dpr, true, c, -1);
|
return snapCenteredAxis(value, item, dpr, true, c, -1);
|
||||||
if (c.vertical)
|
if (c.vertical) return snapCenteredAxis(value, item, dpr, false, c, -1);
|
||||||
return snapCenteredAxis(value, item, dpr, false, c, -1);
|
|
||||||
}
|
}
|
||||||
return std::floor(value * dpr + 1e-6) / dpr;
|
return std::floor(value * dpr + 1e-6) / dpr;
|
||||||
}
|
}
|
||||||
@@ -568,15 +565,12 @@ bool ZUtils::logPlacement(QQuickItem* item, const QString& tag) {
|
|||||||
// instead of dividing, so the edges are what matter.
|
// instead of dividing, so the edges are what matter.
|
||||||
const auto centeredAxisOk = [&](double lo, double hi) {
|
const auto centeredAxisOk = [&](double lo, double hi) {
|
||||||
const double size = hi - lo;
|
const double size = hi - lo;
|
||||||
if (isOff(size))
|
if (isOff(size)) return false;
|
||||||
return false;
|
if (!c.alignWhenCentered) return !isOff(lo) && !isOff(hi);
|
||||||
if (!c.alignWhenCentered)
|
|
||||||
return !isOff(lo) && !isOff(hi);
|
|
||||||
const double center = (lo + hi) / 2.0;
|
const double center = (lo + hi) / 2.0;
|
||||||
const double parity = (std::llround(size) % 2 == 0) ? 0.0 : 0.5;
|
const double parity = (std::llround(size) % 2 == 0) ? 0.0 : 0.5;
|
||||||
double err = std::fmod(center - parity, 1.0);
|
double err = std::fmod(center - parity, 1.0);
|
||||||
if (err < 0)
|
if (err < 0) err += 1.0;
|
||||||
err += 1.0;
|
|
||||||
return std::min(err, 1.0 - err) <= tol;
|
return std::min(err, 1.0 - err) <= tol;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -605,9 +599,7 @@ bool ZUtils::logPlacement(QQuickItem* item, const QString& tag) {
|
|||||||
<< "\n device px: left=" << fmt(l) << " top=" << fmt(t)
|
<< "\n device px: left=" << fmt(l) << " top=" << fmt(t)
|
||||||
<< " right=" << fmt(rt) << " bottom=" << fmt(b)
|
<< " right=" << fmt(rt) << " bottom=" << fmt(b)
|
||||||
<< (isText ? " (right/bottom ignored: Text)" : "")
|
<< (isText ? " (right/bottom ignored: Text)" : "")
|
||||||
<< (!centerSrc.isEmpty()
|
<< (!centerSrc.isEmpty() ? " (centered: " + centerSrc + ")" : QString())
|
||||||
? " (centered: " + centerSrc + ")"
|
|
||||||
: QString())
|
|
||||||
<< (centeredH || centeredV
|
<< (centeredH || centeredV
|
||||||
? QString("\n center: x=%1 y=%2 size=%3 x %4")
|
? QString("\n center: x=%1 y=%2 size=%3 x %4")
|
||||||
.arg(QString::number((l + rt) / 2, 'f', 2))
|
.arg(QString::number((l + rt) / 2, 'f', 2))
|
||||||
@@ -642,8 +634,8 @@ bool ZUtils::logCurvePlacement(
|
|||||||
constexpr double tol = 1e-3;
|
constexpr double tol = 1e-3;
|
||||||
|
|
||||||
const double dpr = dprFor(item);
|
const double dpr = dprFor(item);
|
||||||
const int devW = static_cast<int>(
|
const int devW =
|
||||||
std::max<qint64>(1, std::llround(lineWidth * dpr)));
|
static_cast<int>(std::max<qint64>(1, std::llround(lineWidth * dpr)));
|
||||||
const double half = devW / 2.0;
|
const double half = devW / 2.0;
|
||||||
|
|
||||||
qsizetype offCount = 0;
|
qsizetype offCount = 0;
|
||||||
@@ -690,22 +682,24 @@ bool ZUtils::logCurvePlacement(
|
|||||||
const qsizetype flat = total - sloped;
|
const qsizetype flat = total - sloped;
|
||||||
const bool aligned = offCount == 0;
|
const bool aligned = offCount == 0;
|
||||||
|
|
||||||
qInfo().noquote()
|
qInfo().noquote() << (aligned ? "[CURVE ALIGNED] " : "[CURVE UNALIGNED]")
|
||||||
<< (aligned ? "[CURVE ALIGNED] " : "[CURVE UNALIGNED]")
|
<< (tag.isEmpty() ? QString::fromUtf8(
|
||||||
<< (tag.isEmpty() ? QString::fromUtf8(item->metaObject()->className())
|
item->metaObject()->className())
|
||||||
: tag)
|
: tag)
|
||||||
<< "\n samples: " << total << " points (" << flat << " flat, "
|
<< "\n samples: " << total << " points (" << flat
|
||||||
<< sloped << " sloped ignored), dpr=" << dpr
|
<< " flat, " << sloped << " sloped ignored), dpr=" << dpr
|
||||||
<< ", stroke width=" << lineWidth << " logical = " << devW
|
<< ", stroke width=" << lineWidth << " logical = " << devW
|
||||||
<< " device px"
|
<< " device px"
|
||||||
<< "\n edges: " << offCount << " of " << flat * 2
|
<< "\n edges: " << offCount << " of " << flat * 2
|
||||||
<< " flat stroke edges off the device grid";
|
<< " flat stroke edges off the device grid";
|
||||||
if (!aligned) {
|
if (!aligned) {
|
||||||
qInfo().noquote()
|
qInfo().noquote() << " worst: deviation="
|
||||||
<< " worst: deviation=" << QString::number(worst, 'f', 3)
|
<< QString::number(worst, 'f', 3)
|
||||||
<< " device px at logical x=" << QString::number(worstX, 'f', 2)
|
<< " device px at logical x="
|
||||||
<< " y=" << QString::number(worstY, 'f', 2)
|
<< QString::number(worstX, 'f', 2)
|
||||||
<< " (" << (worstSide < 0 ? "top" : "bottom") << " edge device="
|
<< " y=" << QString::number(worstY, 'f', 2) << " ("
|
||||||
|
<< (worstSide < 0 ? "top" : "bottom")
|
||||||
|
<< " edge device="
|
||||||
<< QString::number(worstEdge, 'f', 3) << ")";
|
<< QString::number(worstEdge, 'f', 3) << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ class ZUtils : public QObject {
|
|||||||
// (exact halves when `alignWhenCentered` is true, the rounded half of the
|
// (exact halves when `alignWhenCentered` is true, the rounded half of the
|
||||||
// anchor target when it is false).
|
// anchor target when it is false).
|
||||||
Q_INVOKABLE static qreal snap(qreal value, QQuickItem* item);
|
Q_INVOKABLE static qreal snap(qreal value, QQuickItem* item);
|
||||||
Q_INVOKABLE static qreal snapCeil(qreal value, QQuickItem* item); // text-derived sizes
|
Q_INVOKABLE static qreal snapCeil(
|
||||||
|
qreal value, QQuickItem* item); // text-derived sizes
|
||||||
Q_INVOKABLE static qreal snapFloor(qreal value, QQuickItem* item);
|
Q_INVOKABLE static qreal snapFloor(qreal value, QQuickItem* item);
|
||||||
// Logs where an item sits in device pixels and whether it is aligned.
|
// Logs where an item sits in device pixels and whether it is aligned.
|
||||||
// Returns true if aligned. `tag` is an optional label for the log line.
|
// Returns true if aligned. `tag` is an optional label for the log line.
|
||||||
@@ -72,8 +73,7 @@ class ZUtils : public QObject {
|
|||||||
// (whole device size, plus the offset the anchor produces), which is
|
// (whole device size, plus the offset the anchor produces), which is
|
||||||
// detected from the item's anchors and Layout alignment.
|
// detected from the item's anchors and Layout alignment.
|
||||||
Q_INVOKABLE static bool logPlacement(
|
Q_INVOKABLE static bool logPlacement(
|
||||||
QQuickItem* item,
|
QQuickItem* item, const QString& tag = QString());
|
||||||
const QString& tag = QString());
|
|
||||||
|
|
||||||
// Walks a stroked curve path (flat x,y pairs in the item's logical coords,
|
// Walks a stroked curve path (flat x,y pairs in the item's logical coords,
|
||||||
// every point along the curve, not just its defined points) and checks
|
// every point along the curve, not just its defined points) and checks
|
||||||
|
|||||||
Reference in New Issue
Block a user