Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickqmlgenerator.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6#include "utils_p.h"
7
8#include <private/qsgcurveprocessor_p.h>
9#include <private/qquickshape_p.h>
10#include <private/qquadpath_p.h>
11#include <private/qquickitem_p.h>
12#include <private/qquickimagebase_p_p.h>
13#include <private/qquicktext_p.h>
14#include <private/qquicktranslate_p.h>
15#include <private/qquickimage_p.h>
16
17#include <QtCore/qloggingcategory.h>
18#include <QtCore/qdir.h>
19
21
22QQuickQmlGenerator::QQuickQmlGenerator(const QString fileName, QQuickVectorImageGenerator::GeneratorFlags flags, const QString &outFileName)
23 : QQuickGenerator(fileName, flags)
24 , outputFileName(outFileName)
25{
26 m_result.open(QIODevice::ReadWrite);
27}
28
29QQuickQmlGenerator::~QQuickQmlGenerator()
30{
31}
32
33bool QQuickQmlGenerator::save()
34{
35 bool res = true;
36 if (!outputFileName.isEmpty()) {
37 QFileInfo fileInfo(outputFileName);
38 QDir dir(fileInfo.absolutePath());
39 if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) {
40 qCWarning(lcQuickVectorImage) << "Failed to create path" << dir.absolutePath();
41 res = false;
42 } else {
43 QFile outFile(outputFileName);
44 if (outFile.open(QIODevice::WriteOnly)) {
45 outFile.write(m_result.data());
46 outFile.close();
47 } else {
48 qCWarning(lcQuickVectorImage) << "Failed to write to file" << outFile.fileName();
49 res = false;
50 }
51 }
52 }
53
54 if (lcQuickVectorImage().isDebugEnabled())
55 qCDebug(lcQuickVectorImage).noquote() << m_result.data().left(300);
56
57 return res;
58}
59
60void QQuickQmlGenerator::setShapeTypeName(const QString &name)
61{
62 m_shapeTypeName = name.toLatin1();
63}
64
65QString QQuickQmlGenerator::shapeTypeName() const
66{
67 return QString::fromLatin1(m_shapeTypeName);
68}
69
70void QQuickQmlGenerator::setCommentString(const QString commentString)
71{
72 m_commentString = commentString;
73}
74
75QString QQuickQmlGenerator::commentString() const
76{
77 return m_commentString;
78}
79
80QString QQuickQmlGenerator::generateNodeBase(const NodeInfo &info)
81{
82 if (!info.nodeId.isEmpty())
83 stream() << "objectName: \"" << info.nodeId << "\"";
84
85 QString idString = info.id;
86 if (!idString.isEmpty()) {
87 // If input contains multiple items with the same id, then this is invalid. However we
88 // shouldn't generate invalid QML from it. So we add a suffix if this is detected.
89 if (m_generatedIds.contains(idString))
90 idString += QStringLiteral("_%1").arg(m_generatedIds.size());
91
92 m_generatedIds.insert(idString);
93
94 stream() << "id: " << idString;
95 }
96
97 if (!info.bounds.isNull()) {
98 stream() << "property var originalBounds: Qt.rect("
99 << info.bounds.x() << ", "
100 << info.bounds.y() << ", "
101 << info.bounds.width() << ", "
102 << info.bounds.height() << ")";
103 stream() << "implicitWidth: originalBounds.width";
104 stream() << "implicitHeight: originalBounds.height";
105 }
106
107 if (!info.isDefaultOpacity)
108 stream() << "opacity: " << info.opacity.defaultValue().toReal();
109
110 if (!info.maskId.isEmpty()) {
111 stream() << "layer.enabled: true";
112 stream() << "visible: false";
113 stream() << "layer.textureSize: " << info.maskId << "_" << info.id << "_mask.textureSize";
114 stream() << "layer.sourceRect: " << info.maskId << ".maskRect(" << info.id << ")";
115 } else {
116 generateItemAnimations(idString, info);
117 }
118
119 return idString;
120}
121
122void QQuickQmlGenerator::generateNodeEnd(const NodeInfo &info)
123{
124 m_indentLevel--;
125 stream() << "}";
126
127 if (!info.maskId.isEmpty())
128 generateMaskUse(info);
129}
130
131void QQuickQmlGenerator::generateItemAnimations(const QString &idString, const NodeInfo &info)
132{
133 const bool hasTransform = info.transform.isAnimated()
134 || !info.maskId.isEmpty()
135 || !info.isDefaultTransform
136 || !info.transformReferenceId.isEmpty();
137
138 if (hasTransform) {
139 stream() << "transform: TransformGroup {";
140 m_indentLevel++;
141
142 bool hasNonConstantTransform = false;
143 int earliestOverrideGroup = -1;
144
145 if (!idString.isEmpty()) {
146 stream() << "id: " << idString << "_transform_base_group";
147
148 if (!info.maskId.isEmpty())
149 stream() << "Translate { x: " << idString << ".sourceX; y: " << idString << ".sourceY }";
150
151 if (info.transform.isAnimated()) {
152 for (int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
153 stream() << "TransformGroup {";
154 m_indentLevel++;
155
156 if (!idString.isEmpty())
157 stream() << "id: " << idString << "_transform_group_" << groupIndex;
158
159 int animationStart = info.transform.animationGroup(groupIndex);
160 int nextAnimationStart = groupIndex + 1 < info.transform.animationGroupCount()
161 ? info.transform.animationGroup(groupIndex + 1)
162 : info.transform.animationCount();
163
164 const QQuickAnimatedProperty::PropertyAnimation &firstAnimation = info.transform.animation(animationStart);
165 const bool replace = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
166 if (replace && earliestOverrideGroup < 0)
167 earliestOverrideGroup = groupIndex;
168
169 for (int i = nextAnimationStart - 1; i >= animationStart; --i) {
170 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
171 if (animation.frames.isEmpty())
172 continue;
173
174 const QVariantList &parameters = animation.frames.first().value<QVariantList>();
175 switch (animation.subtype) {
176 case QTransform::TxTranslate:
177 if (animation.isConstant()) {
178 const QPointF translation = parameters.value(0).value<QPointF>();
179 if (!translation.isNull())
180 stream() << "Translate { x: " << translation.x() << "; y: " << translation.y() << " }";
181 } else {
182 hasNonConstantTransform = true;
183 stream() << "Translate { id: " << idString << "_transform_" << groupIndex << "_" << i << " }";
184 }
185 break;
186 case QTransform::TxScale:
187 if (animation.isConstant()) {
188 const QPointF scale = parameters.value(0).value<QPointF>();
189 if (scale != QPointF(1, 1))
190 stream() << "Scale { xScale: " << scale.x() << "; yScale: " << scale.y() << " }";
191 } else {
192 hasNonConstantTransform = true;
193 stream() << "Scale { id: " << idString << "_transform_" << groupIndex << "_" << i << "}";
194 }
195 break;
196 case QTransform::TxRotate:
197 if (animation.isConstant()) {
198 const QPointF center = parameters.value(0).value<QPointF>();
199 const qreal angle = parameters.value(1).toReal();
200 if (!qFuzzyIsNull(angle))
201 stream() << "Rotation { angle: " << angle << "; origin.x: " << center.x() << "; origin.y: " << center.y() << " }"; //### center relative to what?
202 } else {
203 hasNonConstantTransform = true;
204 stream() << "Rotation { id: " << idString << "_transform_" << groupIndex << "_" << i << " }";
205 }
206 break;
207 case QTransform::TxShear:
208 if (animation.isConstant()) {
209 const QPointF skew = parameters.value(0).value<QPointF>();
210 if (!skew.isNull())
211 stream() << "Shear { xAngle: " << skew.x() << "; yAngle: " << skew.y() << " }";
212 } else {
213 hasNonConstantTransform = true;
214 stream() << "Shear { id: " << idString << "_transform_" << groupIndex << "_" << i << " }";
215 }
216 break;
217 default:
218 Q_UNREACHABLE();
219 }
220 }
221
222 m_indentLevel--;
223 stream() << "}";
224 }
225 }
226 }
227
228 if (!info.isDefaultTransform) {
229 QTransform xf = info.transform.defaultValue().value<QTransform>();
230 if (xf.type() <= QTransform::TxTranslate) {
231 stream() << "Translate { x: " << xf.dx() << "; y: " << xf.dy() << "}";
232 } else {
233 stream() << "Matrix4x4 { matrix: ";
234 generateTransform(xf);
235 stream(SameLine) << "}";
236 }
237 }
238
239 if (!info.transformReferenceId.isEmpty())
240 stream() << "Matrix4x4 { matrix: " << info.transformReferenceId << ".transformMatrix }";
241
242 m_indentLevel--;
243 stream() << "}";
244
245 if (hasNonConstantTransform) {
246 generateAnimateTransform(idString, info);
247 } else if (info.transform.isAnimated() && earliestOverrideGroup >= 0) {
248 // We have animations, but they are all constant? Then we still need to respect the
249 // override flag of the animations
250 stream() << "Component.onCompleted: {";
251 m_indentLevel++;
252
253 stream() << idString << "_transform_base_group.activateOverride("
254 << idString << "_transform_group_" << earliestOverrideGroup << ")";
255
256 m_indentLevel--;
257 stream() << "}";
258 }
259 }
260
261 generatePropertyAnimation(info.opacity, idString, QStringLiteral("opacity"));
262 generatePropertyAnimation(info.visibility, idString, QStringLiteral("visible"));
263}
264
265void QQuickQmlGenerator::generateMaskUse(const NodeInfo &info)
266{
267 Q_ASSERT(!info.maskId.isEmpty());
268
269 // Shader effect source for the mask itself
270 stream() << "ShaderEffectSource {";
271 m_indentLevel++;
272
273 const QString maskId = info.maskId + QStringLiteral("_") + info.id + QStringLiteral("_mask");
274 stream() << "id: " << maskId;
275 stream() << "sourceItem: " << info.maskId;
276 stream() << "visible: false";
277
278 stream() << "ItemSpy {";
279 m_indentLevel++;
280 stream() << "id: " << maskId << "_itemspy";
281 stream() << "anchors.fill: parent";
282 m_indentLevel--;
283 stream() << "}";
284 stream() << "textureSize: " << maskId << "_itemspy.requiredTextureSize";
285
286 stream() << "sourceRect: " << info.maskId << ".maskRect(" << info.id << ")";
287 stream() << "width: sourceRect.width";
288 stream() << "height: sourceRect.height";
289
290 m_indentLevel--;
291 stream() << "}";
292
293 stream() << "ShaderEffect {";
294 m_indentLevel++;
295
296 const QString maskShaderId = maskId + QStringLiteral("_se");
297 stream() << "id:" << maskShaderId;
298
299 stream() << "property real sourceX: " << maskId << ".sourceRect.x";
300 stream() << "property real sourceY: " << maskId << ".sourceRect.y";
301 stream() << "width: " << maskId << ".sourceRect.width";
302 stream() << "height: " << maskId << ".sourceRect.height";
303
304 stream() << "fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/genericmask.frag.qsb\"";
305 stream() << "property var source: " << info.id;
306 stream() << "property var maskSource: " << maskId;
307 stream() << "property bool isAlpha: " << (info.isMaskAlpha ? "true" : "false");
308 stream() << "property bool isInverted: " << (info.isMaskInverted ? "true" : "false");
309
310 generateItemAnimations(maskShaderId, info);
311
312 m_indentLevel--;
313 stream() << "}";
314}
315
316bool QQuickQmlGenerator::generateDefsNode(const NodeInfo &info)
317{
318 Q_UNUSED(info)
319
320 return false;
321}
322
323void QQuickQmlGenerator::generateImageNode(const ImageNodeInfo &info)
324{
325 if (!isNodeVisible(info))
326 return;
327
328 const QFileInfo outputFileInfo(outputFileName);
329 const QDir outputDir(outputFileInfo.absolutePath());
330
331 QString filePath;
332
333 if (!m_retainFilePaths || info.externalFileReference.isEmpty()) {
334 filePath = m_assetFileDirectory;
335 if (filePath.isEmpty())
336 filePath = outputDir.absolutePath();
337
338 if (!filePath.isEmpty() && !filePath.endsWith(u'/'))
339 filePath += u'/';
340
341 QDir fileDir(filePath);
342 if (!fileDir.exists()) {
343 if (!fileDir.mkpath(QStringLiteral(".")))
344 qCWarning(lcQuickVectorImage) << "Failed to create image resource directory:" << filePath;
345 }
346
347 filePath += QStringLiteral("%1%2.png").arg(m_assetFilePrefix.isEmpty()
348 ? QStringLiteral("svg_asset_")
349 : m_assetFilePrefix)
350 .arg(info.image.cacheKey());
351
352 if (!info.image.save(filePath))
353 qCWarning(lcQuickVectorImage) << "Unabled to save image resource" << filePath;
354 qCDebug(lcQuickVectorImage) << "Saving copy of IMAGE" << filePath;
355 } else {
356 filePath = info.externalFileReference;
357 }
358
359 const QFileInfo assetFileInfo(filePath);
360
361 stream() << "Image {";
362
363 m_indentLevel++;
364 generateNodeBase(info);
365 stream() << "x: " << info.rect.x();
366 stream() << "y: " << info.rect.y();
367 stream() << "width: " << info.rect.width();
368 stream() << "height: " << info.rect.height();
369 stream() << "source: \"" << m_urlPrefix << outputDir.relativeFilePath(assetFileInfo.absoluteFilePath()) <<"\"";
370 generateNodeEnd(info);
371}
372
373void QQuickQmlGenerator::generatePath(const PathNodeInfo &info, const QRectF &overrideBoundingRect)
374{
375 if (!isNodeVisible(info))
376 return;
377
378 if (m_inShapeItemLevel > 0) {
379 if (!info.isDefaultTransform)
380 qWarning() << "Skipped transform for node" << info.nodeId << "type" << info.typeName << "(this is not supposed to happen)";
381 optimizePaths(info, overrideBoundingRect);
382 } else {
383 m_inShapeItemLevel++;
384 stream() << shapeName() << " {";
385
386 m_indentLevel++;
387 generateNodeBase(info);
388
389 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
390 stream() << "preferredRendererType: Shape.CurveRenderer";
391 optimizePaths(info, overrideBoundingRect);
392 //qCDebug(lcQuickVectorGraphics) << *node->qpath();
393 generateNodeEnd(info);
394 m_inShapeItemLevel--;
395 }
396}
397
398void QQuickQmlGenerator::generateGradient(const QGradient *grad)
399{
400 if (grad->type() == QGradient::LinearGradient) {
401 auto *linGrad = static_cast<const QLinearGradient *>(grad);
402 stream() << "fillGradient: LinearGradient {";
403 m_indentLevel++;
404
405 QRectF gradRect(linGrad->start(), linGrad->finalStop());
406
407 stream() << "x1: " << gradRect.left();
408 stream() << "y1: " << gradRect.top();
409 stream() << "x2: " << gradRect.right();
410 stream() << "y2: " << gradRect.bottom();
411 for (auto &stop : linGrad->stops())
412 stream() << "GradientStop { position: " << QString::number(stop.first, 'g', 7)
413 << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
414 m_indentLevel--;
415 stream() << "}";
416 } else if (grad->type() == QGradient::RadialGradient) {
417 auto *radGrad = static_cast<const QRadialGradient*>(grad);
418 stream() << "fillGradient: RadialGradient {";
419 m_indentLevel++;
420
421 stream() << "centerX: " << radGrad->center().x();
422 stream() << "centerY: " << radGrad->center().y();
423 stream() << "centerRadius: " << radGrad->radius();
424 stream() << "focalX:" << radGrad->focalPoint().x();
425 stream() << "focalY:" << radGrad->focalPoint().y();
426 for (auto &stop : radGrad->stops())
427 stream() << "GradientStop { position: " << QString::number(stop.first, 'g', 7)
428 << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
429 m_indentLevel--;
430 stream() << "}";
431 }
432}
433
434void QQuickQmlGenerator::generateAnimationBindings()
435{
436 QString prefix;
437 if (Q_UNLIKELY(!isRuntimeGenerator()))
438 prefix = QStringLiteral(".animations");
439
440 stream() << "loops: " << m_topLevelIdString << prefix << ".loops";
441 stream() << "paused: " << m_topLevelIdString << prefix << ".paused";
442 stream() << "running: true";
443
444 // We need to reset the animation when the loop count changes
445 stream() << "onLoopsChanged: { if (running) { restart() } }";
446}
447
448void QQuickQmlGenerator::generateEasing(const QQuickAnimatedProperty::PropertyAnimation &animation, int time)
449{
450 if (animation.easingPerFrame.contains(time)) {
451 QBezier bezier = animation.easingPerFrame.value(time);
452 QPointF c1 = bezier.pt2();
453 QPointF c2 = bezier.pt3();
454
455 bool isLinear = (c1 == c1.transposed() && c2 == c2.transposed());
456 if (!isLinear) {
457 int nextIdx = m_easings.size();
458 QString &id = m_easings[{c1.x(), c1.y(), c2.x(), c2.y()}];
459 if (id.isNull())
460 id = QString(QLatin1String("easing_%1")).arg(nextIdx, 2, 10, QLatin1Char('0'));
461 stream() << "easing: " << m_topLevelIdString << "." << id;
462 }
463 }
464}
465
466void QQuickQmlGenerator::generatePropertyAnimation(const QQuickAnimatedProperty &property,
467 const QString &targetName,
468 const QString &propertyName,
469 AnimationType animationType)
470{
471 if (!property.isAnimated())
472 return;
473
474 QString mainAnimationId = targetName
475 + QStringLiteral("_")
476 + propertyName
477 + QStringLiteral("_animation");
478 mainAnimationId.replace(QLatin1Char('.'), QLatin1Char('_'));
479
480 QString prefix;
481 if (Q_UNLIKELY(!isRuntimeGenerator()))
482 prefix = QStringLiteral(".animations");
483
484 stream() << "Connections { target: " << m_topLevelIdString << prefix << "; function onRestart() {" << mainAnimationId << ".restart() } }";
485
486 stream() << "ParallelAnimation {";
487 m_indentLevel++;
488
489 stream() << "id: " << mainAnimationId;
490
491 generateAnimationBindings();
492
493 for (int i = 0; i < property.animationCount(); ++i) {
494 const QQuickAnimatedProperty::PropertyAnimation &animation = property.animation(i);
495
496 stream() << "SequentialAnimation {";
497 m_indentLevel++;
498
499 const int startOffset = animation.startOffset;
500 if (startOffset > 0)
501 stream() << "PauseAnimation { duration: " << startOffset << " }";
502
503 stream() << "SequentialAnimation {";
504 m_indentLevel++;
505
506 const int repeatCount = animation.repeatCount;
507 if (repeatCount < 0)
508 stream() << "loops: Animation.Infinite";
509 else
510 stream() << "loops: " << repeatCount;
511
512 int previousTime = 0;
513 QVariant previousValue;
514 for (auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
515 const int time = it.key();
516 const int frameTime = time - previousTime;
517 const QVariant &value = it.value();
518
519 if (previousValue.isValid() && previousValue == value) {
520 if (frameTime > 0)
521 stream() << "PauseAnimation { duration: " << frameTime << " }";
522 } else if (animationType == AnimationType::Auto && value.typeId() == QMetaType::Bool) {
523 // We special case bools, with PauseAnimation and then a setter at the end
524 if (frameTime > 0)
525 stream() << "PauseAnimation { duration: " << frameTime << " }";
526 stream() << "ScriptAction {";
527 m_indentLevel++;
528
529 stream() << "script:" << targetName << "." << propertyName << " = " << value.toString();
530
531 m_indentLevel--;
532 stream() << "}";
533 } else {
534 generateAnimatedPropertySetter(targetName,
535 propertyName,
536 value,
537 animation,
538 frameTime,
539 time,
540 animationType);
541 }
542
543 previousTime = time;
544 previousValue = value;
545 }
546
547 if (!(animation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd)) {
548 stream() << "ScriptAction {";
549 m_indentLevel++;
550 stream() << "script: ";
551
552 switch (animationType) {
553 case AnimationType::Auto:
554 stream(SameLine) << targetName << "." << propertyName << " = ";
555 break;
556 case AnimationType::ColorOpacity:
557 stream(SameLine) << targetName << "." << propertyName << ".a = ";
558 break;
559 };
560
561 QVariant value = property.defaultValue();
562 if (value.typeId() == QMetaType::QColor)
563 stream(SameLine) << "\"" << value.toString() << "\"";
564 else
565 stream(SameLine) << value.toReal();
566
567 m_indentLevel--;
568 stream() << "}";
569 }
570
571 m_indentLevel--;
572 stream() << "}";
573
574 m_indentLevel--;
575 stream() << "}";
576 }
577
578 m_indentLevel--;
579 stream() << "}";
580}
581
582void QQuickQmlGenerator::generateTransform(const QTransform &xf)
583{
584 if (xf.isAffine()) {
585 stream(SameLine) << "PlanarTransform.fromAffineMatrix("
586 << xf.m11() << ", " << xf.m12() << ", "
587 << xf.m21() << ", " << xf.m22() << ", "
588 << xf.dx() << ", " << xf.dy() << ")";
589 } else {
590 QMatrix4x4 m(xf);
591 stream(SameLine) << "Qt.matrix4x4(";
592 m_indentLevel += 3;
593 const auto *data = m.data();
594 for (int i = 0; i < 4; i++) {
595 stream() << data[i] << ", " << data[i+4] << ", " << data[i+8] << ", " << data[i+12];
596 if (i < 3)
597 stream(SameLine) << ", ";
598 }
599 stream(SameLine) << ")";
600 m_indentLevel -= 3;
601 }
602}
603
604void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainterPath *painterPath, const QQuadPath *quadPath, QQuickVectorImageGenerator::PathSelector pathSelector, const QRectF &boundingRect)
605{
606 Q_UNUSED(pathSelector)
607 Q_ASSERT(painterPath || quadPath);
608
609 const QColor strokeColor = info.strokeStyle.color.defaultValue().value<QColor>();
610 const bool noPen = strokeColor == QColorConstants::Transparent
611 && !info.strokeStyle.color.isAnimated()
612 && !info.strokeStyle.opacity.isAnimated();
613 if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
614 return;
615
616 const QColor fillColor = info.fillColor.defaultValue().value<QColor>();
617 const bool noFill = info.grad.type() == QGradient::NoGradient
618 && fillColor == QColorConstants::Transparent
619 && !info.fillColor.isAnimated()
620 && !info.fillOpacity.isAnimated();
621 if (pathSelector == QQuickVectorImageGenerator::FillPath && noFill)
622 return;
623
624 if (noPen && noFill)
625 return;
626
627 auto fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule());
628 stream() << "ShapePath {";
629 m_indentLevel++;
630
631 QString shapePathId = info.id;
632 if (pathSelector & QQuickVectorImageGenerator::FillPath)
633 shapePathId += QStringLiteral("_fill");
634 if (pathSelector & QQuickVectorImageGenerator::StrokePath)
635 shapePathId += QStringLiteral("_stroke");
636
637 stream() << "id: " << shapePathId;
638
639 if (!info.nodeId.isEmpty()) {
640 switch (pathSelector) {
641 case QQuickVectorImageGenerator::FillPath:
642 stream() << "objectName: \"svg_fill_path:" << info.nodeId << "\"";
643 break;
644 case QQuickVectorImageGenerator::StrokePath:
645 stream() << "objectName: \"svg_stroke_path:" << info.nodeId << "\"";
646 break;
647 case QQuickVectorImageGenerator::FillAndStroke:
648 stream() << "objectName: \"svg_path:" << info.nodeId << "\"";
649 break;
650 }
651 }
652
653 if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
654 stream() << "strokeColor: \"transparent\"";
655 } else {
656 stream() << "strokeColor: \"" << strokeColor.name(QColor::HexArgb) << "\"";
657 stream() << "strokeWidth: " << info.strokeStyle.width;
658 stream() << "capStyle: " << QQuickVectorImageGenerator::Utils::strokeCapStyleString(info.strokeStyle.lineCapStyle);
659 stream() << "joinStyle: " << QQuickVectorImageGenerator::Utils::strokeJoinStyleString(info.strokeStyle.lineJoinStyle);
660 stream() << "miterLimit: " << info.strokeStyle.miterLimit;
661 if (info.strokeStyle.dashArray.length() != 0) {
662 stream() << "strokeStyle: " << "ShapePath.DashLine";
663 stream() << "dashPattern: " << QQuickVectorImageGenerator::Utils::listString(info.strokeStyle.dashArray);
664 stream() << "dashOffset: " << info.strokeStyle.dashOffset;
665 }
666 }
667
668 QTransform fillTransform = info.fillTransform;
669 if (!(pathSelector & QQuickVectorImageGenerator::FillPath)) {
670 stream() << "fillColor: \"transparent\"";
671 } else if (info.grad.type() != QGradient::NoGradient) {
672 generateGradient(&info.grad);
673 if (info.grad.coordinateMode() == QGradient::ObjectMode) {
674 QTransform objectToUserSpace;
675 objectToUserSpace.translate(boundingRect.x(), boundingRect.y());
676 objectToUserSpace.scale(boundingRect.width(), boundingRect.height());
677 fillTransform *= objectToUserSpace;
678 }
679 } else {
680 stream() << "fillColor: \"" << fillColor.name(QColor::HexArgb) << "\"";
681 }
682
683 if (!fillTransform.isIdentity()) {
684 const QTransform &xf = fillTransform;
685 stream() << "fillTransform: ";
686 if (info.fillTransform.type() == QTransform::TxTranslate)
687 stream(SameLine) << "PlanarTransform.fromTranslate(" << xf.dx() << ", " << xf.dy() << ")";
688 else if (info.fillTransform.type() == QTransform::TxScale && !xf.dx() && !xf.dy())
689 stream(SameLine) << "PlanarTransform.fromScale(" << xf.m11() << ", " << xf.m22() << ")";
690 else
691 generateTransform(xf);
692 }
693
694 if (info.trim.enabled) {
695 stream() << "trim.start: " << info.trim.start.defaultValue().toReal();
696 stream() << "trim.end: " << info.trim.end.defaultValue().toReal();
697 stream() << "trim.offset: " << info.trim.offset.defaultValue().toReal();
698
699 }
700
701 if (fillRule == QQuickShapePath::WindingFill)
702 stream() << "fillRule: ShapePath.WindingFill";
703 else
704 stream() << "fillRule: ShapePath.OddEvenFill";
705
706 QString hintStr;
707 if (quadPath)
708 hintStr = QQuickVectorImageGenerator::Utils::pathHintString(*quadPath);
709 if (!hintStr.isEmpty())
710 stream() << hintStr;
711
712 QString svgPathString = painterPath ? QQuickVectorImageGenerator::Utils::toSvgString(*painterPath) : QQuickVectorImageGenerator::Utils::toSvgString(*quadPath);
713 stream() << "PathSvg { path: \"" << svgPathString << "\" }";
714
715 m_indentLevel--;
716 stream() << "}";
717
718 if (info.trim.enabled) {
719 generatePropertyAnimation(info.trim.start, shapePathId + QStringLiteral(".trim"), QStringLiteral("start"));
720 generatePropertyAnimation(info.trim.end, shapePathId + QStringLiteral(".trim"), QStringLiteral("end"));
721 generatePropertyAnimation(info.trim.offset, shapePathId + QStringLiteral(".trim"), QStringLiteral("offset"));
722 }
723
724 generatePropertyAnimation(info.strokeStyle.color, shapePathId, QStringLiteral("strokeColor"));
725 generatePropertyAnimation(info.strokeStyle.opacity, shapePathId, QStringLiteral("strokeColor"), AnimationType::ColorOpacity);
726 generatePropertyAnimation(info.fillColor, shapePathId, QStringLiteral("fillColor"));
727 generatePropertyAnimation(info.fillOpacity, shapePathId, QStringLiteral("fillColor"), AnimationType::ColorOpacity);
728}
729
730void QQuickQmlGenerator::generateNode(const NodeInfo &info)
731{
732 if (!isNodeVisible(info))
733 return;
734
735 stream() << "// Missing Implementation for SVG Node: " << info.typeName;
736 stream() << "// Adding an empty Item and skipping";
737 stream() << "Item {";
738 m_indentLevel++;
739 generateNodeBase(info);
740 generateNodeEnd(info);
741}
742
743void QQuickQmlGenerator::generateTextNode(const TextNodeInfo &info)
744{
745 if (!isNodeVisible(info))
746 return;
747
748 static int counter = 0;
749 stream() << "Item {";
750 m_indentLevel++;
751 generateNodeBase(info);
752
753 if (!info.isTextArea)
754 stream() << "Item { id: textAlignItem_" << counter << "; x: " << info.position.x() << "; y: " << info.position.y() << "}";
755
756 stream() << "Text {";
757
758 m_indentLevel++;
759
760 const QString textItemId = QStringLiteral("_qt_textItem_%1").arg(counter);
761 stream() << "id: " << textItemId;
762
763 generatePropertyAnimation(info.fillColor, textItemId, QStringLiteral("color"));
764 generatePropertyAnimation(info.fillOpacity, textItemId, QStringLiteral("color"), AnimationType::ColorOpacity);
765 generatePropertyAnimation(info.strokeColor, textItemId, QStringLiteral("styleColor"));
766 generatePropertyAnimation(info.strokeOpacity, textItemId, QStringLiteral("styleColor"), AnimationType::ColorOpacity);
767
768 if (info.isTextArea) {
769 stream() << "x: " << info.position.x();
770 stream() << "y: " << info.position.y();
771 if (info.size.width() > 0)
772 stream() << "width: " << info.size.width();
773 if (info.size.height() > 0)
774 stream() << "height: " << info.size.height();
775 stream() << "wrapMode: Text.Wrap"; // ### WordWrap? verify with SVG standard
776 stream() << "clip: true"; //### Not exactly correct: should clip on the text level, not the pixel level
777 } else {
778 QString hAlign = QStringLiteral("left");
779 stream() << "anchors.baseline: textAlignItem_" << counter << ".top";
780 switch (info.alignment) {
781 case Qt::AlignHCenter:
782 hAlign = QStringLiteral("horizontalCenter");
783 break;
784 case Qt::AlignRight:
785 hAlign = QStringLiteral("right");
786 break;
787 default:
788 qCDebug(lcQuickVectorImage) << "Unexpected text alignment" << info.alignment;
789 Q_FALLTHROUGH();
790 case Qt::AlignLeft:
791 break;
792 }
793 stream() << "anchors." << hAlign << ": textAlignItem_" << counter << ".left";
794 }
795 counter++;
796
797 stream() << "color: \"" << info.fillColor.defaultValue().value<QColor>().name(QColor::HexArgb) << "\"";
798 stream() << "textFormat:" << (info.needsRichText ? "Text.RichText" : "Text.StyledText");
799
800 QString s = info.text;
801 s.replace(QLatin1Char('"'), QLatin1String("\\\""));
802 stream() << "text: \"" << s << "\"";
803 stream() << "font.family: \"" << info.font.family() << "\"";
804 if (info.font.pixelSize() > 0)
805 stream() << "font.pixelSize:" << info.font.pixelSize();
806 else if (info.font.pointSize() > 0)
807 stream() << "font.pixelSize:" << info.font.pointSizeF();
808 if (info.font.underline())
809 stream() << "font.underline: true";
810 if (info.font.weight() != QFont::Normal)
811 stream() << "font.weight: " << int(info.font.weight());
812 if (info.font.italic())
813 stream() << "font.italic: true";
814 switch (info.font.hintingPreference()) {
815 case QFont::PreferFullHinting:
816 stream() << "font.hintingPreference: Font.PreferFullHinting";
817 break;
818 case QFont::PreferVerticalHinting:
819 stream() << "font.hintingPreference: Font.PreferVerticalHinting";
820 break;
821 case QFont::PreferNoHinting:
822 stream() << "font.hintingPreference: Font.PreferNoHinting";
823 break;
824 case QFont::PreferDefaultHinting:
825 stream() << "font.hintingPreference: Font.PreferDefaultHinting";
826 break;
827 };
828
829 const QColor strokeColor = info.strokeColor.defaultValue().value<QColor>();
830 if (strokeColor != QColorConstants::Transparent || info.strokeColor.isAnimated()) {
831 stream() << "styleColor: \"" << strokeColor.name(QColor::HexArgb) << "\"";
832 stream() << "style: Text.Outline";
833 }
834
835 m_indentLevel--;
836 stream() << "}";
837
838 generateNodeEnd(info);
839}
840
841void QQuickQmlGenerator::generateUseNode(const UseNodeInfo &info)
842{
843 if (!isNodeVisible(info))
844 return;
845
846 if (info.stage == StructureNodeStage::Start) {
847 stream() << "Item {";
848 m_indentLevel++;
849 generateNodeBase(info);
850 } else {
851 generateNodeEnd(info);
852 }
853}
854
855void QQuickQmlGenerator::generatePathContainer(const StructureNodeInfo &info)
856{
857 Q_UNUSED(info);
858 stream() << shapeName() <<" {";
859 m_indentLevel++;
860 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
861 stream() << "preferredRendererType: Shape.CurveRenderer";
862 m_indentLevel--;
863
864 m_inShapeItemLevel++;
865}
866
867void QQuickQmlGenerator::generateAnimatedPropertySetter(const QString &targetName,
868 const QString &propertyName,
869 const QVariant &value,
870 const QQuickAnimatedProperty::PropertyAnimation &animation,
871 int frameTime,
872 int time,
873 AnimationType animationType)
874{
875 if (frameTime > 0) {
876 switch (animationType) {
877 case AnimationType::Auto:
878 if (value.typeId() == QMetaType::QColor)
879 stream() << "ColorAnimation {";
880 else
881 stream() << "PropertyAnimation {";
882 break;
883 case AnimationType::ColorOpacity:
884 stream() << "ColorOpacityAnimation {";
885 break;
886 };
887 m_indentLevel++;
888
889 stream() << "duration: " << frameTime;
890 stream() << "target: " << targetName;
891 stream() << "property: \"" << propertyName << "\"";
892 stream() << "to: ";
893 if (value.typeId() == QMetaType::QVector3D) {
894 const QVector3D &v = value.value<QVector3D>();
895 stream(SameLine) << "Qt.vector3d(" << v.x() << ", " << v.y() << ", " << v.z() << ")";
896 } else if (value.typeId() == QMetaType::QColor) {
897 stream(SameLine) << "\"" << value.toString() << "\"";
898 } else {
899 stream(SameLine) << value.toReal();
900 }
901 generateEasing(animation, time);
902 m_indentLevel--;
903 stream() << "}";
904 } else {
905 stream() << "ScriptAction {";
906 m_indentLevel++;
907 stream() << "script:" << targetName << "." << propertyName;
908 if (animationType == AnimationType::ColorOpacity)
909 stream(SameLine) << ".a";
910
911 stream(SameLine) << " = ";
912 if (value.typeId() == QMetaType::QVector3D) {
913 const QVector3D &v = value.value<QVector3D>();
914 stream(SameLine) << "Qt.vector3d(" << v.x() << ", " << v.y() << ", " << v.z() << ")";
915 } else if (value.typeId() == QMetaType::QColor) {
916 stream(SameLine) << "\"" << value.toString() << "\"";
917 } else {
918 stream(SameLine) << value.toReal();
919 }
920 m_indentLevel--;
921 stream() << "}";
922 }
923}
924
925void QQuickQmlGenerator::generateAnimateTransform(const QString &targetName, const NodeInfo &info)
926{
927 if (!info.transform.isAnimated())
928 return;
929
930 const QString mainAnimationId = targetName
931 + QStringLiteral("_transform_animation");
932
933 QString prefix;
934 if (Q_UNLIKELY(!isRuntimeGenerator()))
935 prefix = QStringLiteral(".animations");
936 stream() << "Connections { target: " << m_topLevelIdString << prefix << "; function onRestart() {" << mainAnimationId << ".restart() } }";
937
938 stream() << "ParallelAnimation {";
939 m_indentLevel++;
940
941 stream() << "id:" << mainAnimationId;
942
943 generateAnimationBindings();
944 for (int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
945 int animationStart = info.transform.animationGroup(groupIndex);
946 int nextAnimationStart = groupIndex + 1 < info.transform.animationGroupCount()
947 ? info.transform.animationGroup(groupIndex + 1)
948 : info.transform.animationCount();
949
950 // The first animation in the group holds the shared properties for the whole group
951 const QQuickAnimatedProperty::PropertyAnimation &firstAnimation = info.transform.animation(animationStart);
952 const bool freeze = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
953 const bool replace = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
954
955 stream() << "SequentialAnimation {";
956 m_indentLevel++;
957
958 const int startOffset = firstAnimation.startOffset;
959 if (startOffset > 0)
960 stream() << "PauseAnimation { duration: " << startOffset << " }";
961
962 const int repeatCount = firstAnimation.repeatCount;
963 if (repeatCount < 0)
964 stream() << "loops: Animation.Infinite";
965 else
966 stream() << "loops: " << repeatCount;
967
968 if (replace) {
969 stream() << "ScriptAction {";
970 m_indentLevel++;
971
972 stream() << "script: " << targetName << "_transform_base_group"
973 << ".activateOverride(" << targetName << "_transform_group_" << groupIndex << ")";
974
975 m_indentLevel--;
976 stream() << "}";
977 }
978
979 stream() << "ParallelAnimation {";
980 m_indentLevel++;
981
982 for (int i = animationStart; i < nextAnimationStart; ++i) {
983 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
984 if (animation.isConstant())
985 continue;
986 bool hasRotationCenter = false;
987 if (animation.subtype == QTransform::TxRotate) {
988 for (auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
989 const QPointF center = it->value<QVariantList>().value(0).value<QPointF>();
990 if (!center.isNull()) {
991 hasRotationCenter = true;
992 break;
993 }
994 }
995 }
996
997 stream() << "SequentialAnimation {";
998 m_indentLevel++;
999
1000 int previousTime = 0;
1001 QVariantList previousParameters;
1002 for (auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
1003 const int time = it.key();
1004 const int frameTime = time - previousTime;
1005 const QVariantList &parameters = it.value().value<QVariantList>();
1006 if (parameters.isEmpty())
1007 continue;
1008
1009 if (parameters == previousParameters) {
1010 if (frameTime > 0)
1011 stream() << "PauseAnimation { duration: " << frameTime << " }";
1012 } else {
1013 stream() << "ParallelAnimation {";
1014 m_indentLevel++;
1015
1016 const QString propertyTargetName = targetName
1017 + QStringLiteral("_transform_")
1018 + QString::number(groupIndex)
1019 + QStringLiteral("_")
1020 + QString::number(i);
1021
1022 switch (animation.subtype) {
1023 case QTransform::TxTranslate:
1024 {
1025 const QPointF translation = parameters.first().value<QPointF>();
1026
1027 generateAnimatedPropertySetter(propertyTargetName,
1028 QStringLiteral("x"),
1029 translation.x(),
1030 animation,
1031 frameTime,
1032 time);
1033 generateAnimatedPropertySetter(propertyTargetName,
1034 QStringLiteral("y"),
1035 translation.y(),
1036 animation,
1037 frameTime,
1038 time);
1039 break;
1040 }
1041 case QTransform::TxScale:
1042 {
1043 const QPointF scale = parameters.first().value<QPointF>();
1044 generateAnimatedPropertySetter(propertyTargetName,
1045 QStringLiteral("xScale"),
1046 scale.x(),
1047 animation,
1048 frameTime,
1049 time);
1050 generateAnimatedPropertySetter(propertyTargetName,
1051 QStringLiteral("yScale"),
1052 scale.y(),
1053 animation,
1054 frameTime,
1055 time);
1056 break;
1057 }
1058 case QTransform::TxRotate:
1059 {
1060 Q_ASSERT(parameters.size() == 2);
1061 const qreal angle = parameters.value(1).toReal();
1062 if (hasRotationCenter) {
1063 const QPointF center = parameters.value(0).value<QPointF>();
1064 generateAnimatedPropertySetter(propertyTargetName,
1065 QStringLiteral("origin"),
1066 QVector3D(center.x(), center.y(), 0.0),
1067 animation,
1068 frameTime,
1069 time);
1070 }
1071 generateAnimatedPropertySetter(propertyTargetName,
1072 QStringLiteral("angle"),
1073 angle,
1074 animation,
1075 frameTime,
1076 time);
1077 break;
1078 }
1079 case QTransform::TxShear:
1080 {
1081 const QPointF skew = parameters.first().value<QPointF>();
1082
1083 generateAnimatedPropertySetter(propertyTargetName,
1084 QStringLiteral("xAngle"),
1085 skew.x(),
1086 animation,
1087 frameTime,
1088 time);
1089
1090 generateAnimatedPropertySetter(propertyTargetName,
1091 QStringLiteral("yAngle"),
1092 skew.y(),
1093 animation,
1094 frameTime,
1095 time);
1096 break;
1097 }
1098 default:
1099 Q_UNREACHABLE();
1100 }
1101
1102 m_indentLevel--;
1103 stream() << "}"; // Parallel key frame animation
1104 }
1105
1106 previousTime = time;
1107 previousParameters = parameters;
1108 }
1109
1110 m_indentLevel--;
1111 stream() << "}"; // Parallel key frame animation
1112 }
1113
1114 m_indentLevel--;
1115 stream() << "}"; // Parallel key frame animation
1116
1117 // If the animation ever finishes, then we add an action on the end that handles itsr
1118 // freeze state
1119 if (firstAnimation.repeatCount >= 0) {
1120 stream() << "ScriptAction {";
1121 m_indentLevel++;
1122
1123 stream() << "script: {";
1124 m_indentLevel++;
1125
1126 if (!freeze) {
1127 stream() << targetName << "_transform_base_group.deactivate("
1128 << targetName << "_transform_group_" << groupIndex << ")";
1129 } else if (!replace) {
1130 stream() << targetName << "_transform_base_group.deactivateOverride("
1131 << targetName << "_transform_group_" << groupIndex << ")";
1132 }
1133
1134 m_indentLevel--;
1135 stream() << "}";
1136
1137 m_indentLevel--;
1138 stream() << "}";
1139 }
1140
1141 m_indentLevel--;
1142 stream() << "}";
1143 }
1144
1145 m_indentLevel--;
1146 stream() << "}";
1147}
1148
1149bool QQuickQmlGenerator::generateStructureNode(const StructureNodeInfo &info)
1150{
1151 if (!isNodeVisible(info))
1152 return false;
1153
1154 const bool isPathContainer = !info.forceSeparatePaths && info.isPathContainer;
1155 if (info.stage == StructureNodeStage::Start) {
1156 if (!info.clipBox.isEmpty()) {
1157 stream() << "Item { // Clip";
1158
1159 m_indentLevel++;
1160 stream() << "width: " << info.clipBox.width();
1161 stream() << "height: " << info.clipBox.height();
1162 stream() << "clip: true";
1163 }
1164
1165 if (isPathContainer) {
1166 generatePathContainer(info);
1167 } else if (!info.customItemType.isEmpty()) {
1168 stream() << info.customItemType << " {";
1169 } else {
1170 stream() << "Item { // Structure node";
1171 }
1172
1173 m_indentLevel++;
1174 if (!info.viewBox.isEmpty()) {
1175 stream() << "transform: [";
1176 m_indentLevel++;
1177 bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
1178 if (translate)
1179 stream() << "Translate { x: " << -info.viewBox.x() << "; y: " << -info.viewBox.y() << " },";
1180 stream() << "Scale { xScale: width / " << info.viewBox.width() << "; yScale: height / " << info.viewBox.height() << " }";
1181 m_indentLevel--;
1182 stream() << "]";
1183 }
1184
1185 generateNodeBase(info);
1186 } else {
1187 generateNodeEnd(info);
1188 if (isPathContainer)
1189 m_inShapeItemLevel--;
1190
1191 if (!info.clipBox.isEmpty()) {
1192 m_indentLevel--;
1193 stream() << "}";
1194 }
1195 }
1196
1197 return true;
1198}
1199
1200bool QQuickQmlGenerator::generateMaskNode(const MaskNodeInfo &info)
1201{
1202 // Generate an invisible item subtree which can be used in ShaderEffectSource
1203 if (info.stage == StructureNodeStage::Start) {
1204 stream() << "Item {";
1205 m_indentLevel++;
1206
1207 generateNodeBase(info);
1208
1209 stream() << "visible: false";
1210 stream() << "width: originalBounds.width";
1211 stream() << "height: originalBounds.height";
1212
1213 stream() << "property real maskX: " << info.maskRect.left();
1214 stream() << "property real maskY: " << info.maskRect.top();
1215 stream() << "property real maskWidth: " << info.maskRect.width();
1216 stream() << "property real maskHeight: " << info.maskRect.height();
1217
1218 stream() << "function maskRect(other) {";
1219 m_indentLevel++;
1220
1221 stream() << "return ";
1222 if (info.isMaskRectRelativeCoordinates) {
1223 stream(SameLine)
1224 << "Qt.rect("
1225 << info.id << ".maskX * other.originalBounds.width + other.originalBounds.x,"
1226 << info.id << ".maskY * other.originalBounds.height + other.originalBounds.y,"
1227 << info.id << ".maskWidth * other.originalBounds.width,"
1228 << info.id << ".maskHeight * other.originalBounds.height)";
1229 } else {
1230 stream(SameLine)
1231 << "Qt.rect("
1232 << info.id << ".maskX, "
1233 << info.id << ".maskY, "
1234 << info.id << ".maskWidth, "
1235 << info.id << ".maskHeight)";
1236 }
1237
1238 m_indentLevel--;
1239 stream() << "}";
1240
1241 } else {
1242 generateNodeEnd(info);
1243 }
1244
1245 return true;
1246}
1247
1248bool QQuickQmlGenerator::generateRootNode(const StructureNodeInfo &info)
1249{
1250 const QStringList comments = m_commentString.split(u'\n');
1251
1252 if (!isNodeVisible(info)) {
1253 m_indentLevel = 0;
1254
1255 if (comments.isEmpty()) {
1256 stream() << "// Generated from SVG";
1257 } else {
1258 for (const auto &comment : comments)
1259 stream() << "// " << comment;
1260 }
1261
1262 stream() << "import QtQuick";
1263 stream() << "import QtQuick.Shapes" << Qt::endl;
1264 stream() << "Item {";
1265 m_indentLevel++;
1266
1267 double w = info.size.width();
1268 double h = info.size.height();
1269 if (w > 0)
1270 stream() << "implicitWidth: " << w;
1271 if (h > 0)
1272 stream() << "implicitHeight: " << h;
1273
1274 m_indentLevel--;
1275 stream() << "}";
1276
1277 return false;
1278 }
1279
1280 if (info.stage == StructureNodeStage::Start) {
1281 m_indentLevel = 0;
1282
1283 if (comments.isEmpty())
1284 stream() << "// Generated from SVG";
1285 else
1286 for (const auto &comment : comments)
1287 stream() << "// " << comment;
1288
1289 stream() << "import QtQuick";
1290 stream() << "import QtQuick.VectorImage";
1291 stream() << "import QtQuick.VectorImage.Helpers";
1292 stream() << "import QtQuick.Shapes";
1293 for (const auto &import : std::as_const(m_extraImports))
1294 stream() << "import " << import;
1295
1296 stream() << Qt::endl << "Item {";
1297 m_indentLevel++;
1298
1299 double w = info.size.width();
1300 double h = info.size.height();
1301 if (w > 0)
1302 stream() << "implicitWidth: " << w;
1303 if (h > 0)
1304 stream() << "implicitHeight: " << h;
1305
1306 if (Q_UNLIKELY(!isRuntimeGenerator())) {
1307 stream() << "component AnimationsInfo : QtObject";
1308 stream() << "{";
1309 m_indentLevel++;
1310 }
1311
1312 stream() << "property bool paused: false";
1313 stream() << "property int loops: 1";
1314 stream() << "signal restart()";
1315
1316 if (Q_UNLIKELY(!isRuntimeGenerator())) {
1317 m_indentLevel--;
1318 stream() << "}";
1319 stream() << "property AnimationsInfo animations : AnimationsInfo {}";
1320 }
1321
1322 if (!info.viewBox.isEmpty()) {
1323 stream() << "transform: [";
1324 m_indentLevel++;
1325 bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
1326 if (translate)
1327 stream() << "Translate { x: " << -info.viewBox.x() << "; y: " << -info.viewBox.y() << " },";
1328 stream() << "Scale { xScale: width / " << info.viewBox.width() << "; yScale: height / " << info.viewBox.height() << " }";
1329 m_indentLevel--;
1330 stream() << "]";
1331 }
1332
1333 if (!info.forceSeparatePaths && info.isPathContainer) {
1334 m_topLevelIdString = QStringLiteral("__qt_toplevel");
1335 stream() << "id: " << m_topLevelIdString;
1336
1337 generatePathContainer(info);
1338 m_indentLevel++;
1339
1340 generateNodeBase(info);
1341 } else {
1342 m_topLevelIdString = generateNodeBase(info);
1343 if (m_topLevelIdString.isEmpty())
1344 qCWarning(lcQuickVectorImage) << "No ID specified for top level item";
1345 }
1346 } else {
1347 if (m_inShapeItemLevel > 0) {
1348 m_inShapeItemLevel--;
1349 m_indentLevel--;
1350 stream() << "}";
1351 }
1352
1353 for (const auto [coords, id] : m_easings.asKeyValueRange()) {
1354 stream() << "property easingCurve " << id << ": { type: Easing.BezierSpline; bezierCurve: [ ";
1355 for (auto coord : coords)
1356 stream(SameLine) << coord << ", ";
1357 stream(SameLine) << "1, 1 ] }";
1358 }
1359
1360 generateNodeEnd(info);
1361 stream().flush();
1362 }
1363
1364 return true;
1365}
1366
1367QStringView QQuickQmlGenerator::indent()
1368{
1369 static QString indentString;
1370 int indentWidth = m_indentLevel * 4;
1371 if (indentWidth > indentString.size())
1372 indentString.fill(QLatin1Char(' '), indentWidth * 2);
1373 return QStringView(indentString).first(indentWidth);
1374}
1375
1376QTextStream &QQuickQmlGenerator::stream(int flags)
1377{
1378 if (m_stream.device() == nullptr)
1379 m_stream.setDevice(&m_result);
1380 else if (!(flags & StreamFlags::SameLine))
1381 m_stream << Qt::endl << indent();
1382 return m_stream;
1383}
1384
1385const char *QQuickQmlGenerator::shapeName() const
1386{
1387 return m_shapeTypeName.isEmpty() ? "Shape" : m_shapeTypeName.constData();
1388}
1389
1390QT_END_NAMESPACE
Combined button and popup list for selecting options.