1/*
2 SPDX-FileCopyrightText: 2010 Marco Martin <mart@kde.org>
3 SPDX-FileCopyrightText: 2014 David Edmundson <davidedmundson@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "framesvgitem.h"
9
10#include "imagetexturescache.h"
11#include "managedtexturenode.h"
12
13#include <QQuickWindow>
14#include <QSGGeometry>
15#include <QSGTexture>
16
17#include <QDebug>
18#include <QPainter>
19
20#include <ksvg/private/framesvg_helpers.h>
21#include <ksvg/private/framesvg_p.h>
22
23#include <cmath> //floor()
24
25#include <Kirigami/Platform/PlatformTheme>
26#include <debug_p.h>
27
28namespace KSvg
29{
30Q_GLOBAL_STATIC(ImageTexturesCache, s_cache)
31
32class FrameNode : public QSGNode
33{
34public:
35 FrameNode(const QString &prefix, FrameSvg *svg)
36 : QSGNode()
37 , leftWidth(0)
38 , rightWidth(0)
39 , topHeight(0)
40 , bottomHeight(0)
41 {
42 if (svg->enabledBorders() & FrameSvg::LeftBorder) {
43 leftWidth = svg->elementSize(elementId: prefix % QLatin1String("left")).width();
44 }
45 if (svg->enabledBorders() & FrameSvg::RightBorder) {
46 rightWidth = svg->elementSize(elementId: prefix % QLatin1String("right")).width();
47 }
48 if (svg->enabledBorders() & FrameSvg::TopBorder) {
49 topHeight = svg->elementSize(elementId: prefix % QLatin1String("top")).height();
50 }
51 if (svg->enabledBorders() & FrameSvg::BottomBorder) {
52 bottomHeight = svg->elementSize(elementId: prefix % QLatin1String("bottom")).height();
53 }
54 }
55
56 QRect contentsRect(const QSize &size) const
57 {
58 const QSize contentSize(size.width() - leftWidth - rightWidth, size.height() - topHeight - bottomHeight);
59
60 return QRect(QPoint(leftWidth, topHeight), contentSize);
61 }
62
63private:
64 int leftWidth;
65 int rightWidth;
66 int topHeight;
67 int bottomHeight;
68};
69
70class FrameItemNode : public ManagedTextureNode
71{
72public:
73 enum FitMode {
74 // render SVG at native resolution then stretch it in openGL
75 FastStretch,
76 // on resize re-render the part of the frame from the SVG
77 Stretch,
78 Tile,
79 };
80
81 FrameItemNode(FrameSvgItem *frameSvg, FrameSvg::EnabledBorders borders, FitMode fitMode, QSGNode *parent)
82 : ManagedTextureNode()
83 , m_frameSvg(frameSvg)
84 , m_border(borders)
85 , m_fitMode(fitMode)
86 {
87 parent->appendChildNode(node: this);
88
89 if (m_fitMode == Tile) {
90 if (m_border == FrameSvg::TopBorder || m_border == FrameSvg::BottomBorder || m_border == FrameSvg::NoBorder) {
91 static_cast<QSGTextureMaterial *>(material())->setHorizontalWrapMode(QSGTexture::Repeat);
92 static_cast<QSGOpaqueTextureMaterial *>(opaqueMaterial())->setHorizontalWrapMode(QSGTexture::Repeat);
93 }
94 if (m_border == FrameSvg::LeftBorder || m_border == FrameSvg::RightBorder || m_border == FrameSvg::NoBorder) {
95 static_cast<QSGTextureMaterial *>(material())->setVerticalWrapMode(QSGTexture::Repeat);
96 static_cast<QSGOpaqueTextureMaterial *>(opaqueMaterial())->setVerticalWrapMode(QSGTexture::Repeat);
97 }
98 }
99
100 if (m_fitMode == Tile || m_fitMode == FastStretch) {
101 QString elementId = m_frameSvg->frameSvg()->actualPrefix() + FrameSvgHelpers::borderToElementId(borders: m_border);
102 m_elementNativeSize = m_frameSvg->frameSvg()->elementSize(elementId).toSize();
103
104 if (m_elementNativeSize.isEmpty()) {
105 // if the default element is empty, we can avoid the slower tiling path
106 // this also avoids a divide by 0 error
107 m_fitMode = FastStretch;
108 }
109
110 updateTexture(size: m_elementNativeSize, elementId);
111 }
112 }
113
114 void updateTexture(const QSize &size, const QString &elementId)
115 {
116 QQuickWindow::CreateTextureOptions options;
117 if (m_fitMode != Tile) {
118 options = QQuickWindow::TextureCanUseAtlas;
119 }
120 setTexture(s_cache->loadTexture(window: m_frameSvg->window(), image: m_frameSvg->frameSvg()->image(size, elementID: elementId), options));
121 }
122
123 void reposition(const QRect &frameGeometry, QSize &fullSize)
124 {
125 QRect nodeRect = FrameSvgHelpers::sectionRect(borders: m_border, contentRect: frameGeometry, fullSize);
126
127 // ensure we're not passing a weird rectangle to updateTexturedRectGeometry
128 if (!nodeRect.isValid() || nodeRect.isEmpty()) {
129 nodeRect = QRect();
130 }
131
132 // the position of the relevant texture within this texture ID.
133 // for atlas' this will only be a small part of the texture
134 QRectF textureRect;
135
136 if (m_fitMode == Tile) {
137 textureRect = QRectF(0, 0, 1, 1); // we can never be in an atlas for tiled images.
138
139 // if tiling horizontally
140 if (m_border == FrameSvg::TopBorder || m_border == FrameSvg::BottomBorder || m_border == FrameSvg::NoBorder) {
141 // cmp. CSS3's border-image-repeat: "repeat", though with first tile not centered, but aligned to left
142 textureRect.setWidth((qreal)nodeRect.width() / m_elementNativeSize.width());
143 }
144 // if tiling vertically
145 if (m_border == FrameSvg::LeftBorder || m_border == FrameSvg::RightBorder || m_border == FrameSvg::NoBorder) {
146 // cmp. CSS3's border-image-repeat: "repeat", though with first tile not centered, but aligned to top
147 textureRect.setHeight((qreal)nodeRect.height() / m_elementNativeSize.height());
148 }
149 } else if (m_fitMode == Stretch) {
150 QString prefix = m_frameSvg->frameSvg()->actualPrefix();
151
152 QString elementId = prefix + FrameSvgHelpers::borderToElementId(borders: m_border);
153
154 // re-render the SVG at new size
155 updateTexture(size: nodeRect.size(), elementId);
156 textureRect = texture()->normalizedTextureSubRect();
157 } else if (texture()) { // for fast stretch.
158 textureRect = texture()->normalizedTextureSubRect();
159 }
160
161 QSGGeometry::updateTexturedRectGeometry(g: geometry(), rect: nodeRect, sourceRect: textureRect);
162 markDirty(bits: QSGNode::DirtyGeometry);
163 }
164
165private:
166 FrameSvgItem *m_frameSvg;
167 FrameSvg::EnabledBorders m_border;
168 QSize m_elementNativeSize;
169 FitMode m_fitMode;
170};
171
172FrameSvgItemMargins::FrameSvgItemMargins(KSvg::FrameSvg *frameSvg, QObject *parent)
173 : QObject(parent)
174 , m_frameSvg(frameSvg)
175 , m_fixed(false)
176 , m_inset(false)
177{
178 // qDebug() << "margins at: " << left() << top() << right() << bottom();
179}
180
181qreal FrameSvgItemMargins::left() const
182{
183 if (m_fixed) {
184 return m_frameSvg->fixedMarginSize(edge: FrameSvg::LeftMargin);
185 } else if (m_inset) {
186 return m_frameSvg->insetSize(edge: FrameSvg::LeftMargin);
187 } else {
188 return m_frameSvg->marginSize(edge: FrameSvg::LeftMargin);
189 }
190}
191
192qreal FrameSvgItemMargins::top() const
193{
194 if (m_fixed) {
195 return m_frameSvg->fixedMarginSize(edge: FrameSvg::TopMargin);
196 } else if (m_inset) {
197 return m_frameSvg->insetSize(edge: FrameSvg::TopMargin);
198 } else {
199 return m_frameSvg->marginSize(edge: FrameSvg::TopMargin);
200 }
201}
202
203qreal FrameSvgItemMargins::right() const
204{
205 if (m_fixed) {
206 return m_frameSvg->fixedMarginSize(edge: FrameSvg::RightMargin);
207 } else if (m_inset) {
208 return m_frameSvg->insetSize(edge: FrameSvg::RightMargin);
209 } else {
210 return m_frameSvg->marginSize(edge: FrameSvg::RightMargin);
211 }
212}
213
214qreal FrameSvgItemMargins::bottom() const
215{
216 if (m_fixed) {
217 return m_frameSvg->fixedMarginSize(edge: FrameSvg::BottomMargin);
218 } else if (m_inset) {
219 return m_frameSvg->insetSize(edge: FrameSvg::BottomMargin);
220 } else {
221 return m_frameSvg->marginSize(edge: FrameSvg::BottomMargin);
222 }
223}
224
225qreal FrameSvgItemMargins::horizontal() const
226{
227 return left() + right();
228}
229
230qreal FrameSvgItemMargins::vertical() const
231{
232 return top() + bottom();
233}
234
235QList<qreal> FrameSvgItemMargins::margins() const
236{
237 qreal left;
238 qreal top;
239 qreal right;
240 qreal bottom;
241 m_frameSvg->getMargins(left, top, right, bottom);
242 return {left, top, right, bottom};
243}
244
245void FrameSvgItemMargins::update()
246{
247 Q_EMIT marginsChanged();
248}
249
250void FrameSvgItemMargins::setFixed(bool fixed)
251{
252 if (fixed == m_fixed) {
253 return;
254 }
255
256 m_fixed = fixed;
257 Q_EMIT marginsChanged();
258}
259
260bool FrameSvgItemMargins::isFixed() const
261{
262 return m_fixed;
263}
264
265void FrameSvgItemMargins::setInset(bool inset)
266{
267 if (inset == m_inset) {
268 return;
269 }
270
271 m_inset = inset;
272 Q_EMIT marginsChanged();
273}
274
275bool FrameSvgItemMargins::isInset() const
276{
277 return m_inset;
278}
279
280FrameSvgItem::FrameSvgItem(QQuickItem *parent)
281 : QQuickItem(parent)
282 , m_margins(nullptr)
283 , m_fixedMargins(nullptr)
284 , m_insetMargins(nullptr)
285 , m_textureChanged(false)
286 , m_sizeChanged(false)
287 , m_fastPath(true)
288{
289 m_frameSvg = new KSvg::FrameSvg(this);
290
291 setFlag(flag: QQuickItem::ItemHasContents, enabled: true);
292 setFlag(flag: ItemHasContents, enabled: true);
293 connect(sender: m_frameSvg, signal: &FrameSvg::repaintNeeded, context: this, slot: &FrameSvgItem::doUpdate);
294 connect(sender: m_frameSvg, signal: &Svg::fromCurrentImageSetChanged, context: this, slot: &FrameSvgItem::fromCurrentImageSetChanged);
295 connect(sender: m_frameSvg, signal: &Svg::statusChanged, context: this, slot: &FrameSvgItem::statusChanged);
296}
297
298FrameSvgItem::~FrameSvgItem()
299{
300}
301
302class CheckMarginsChange
303{
304public:
305 CheckMarginsChange(QList<qreal> &oldMargins, FrameSvgItemMargins *marginsObject)
306 : m_oldMargins(oldMargins)
307 , m_marginsObject(marginsObject)
308 {
309 }
310
311 ~CheckMarginsChange()
312 {
313 const QList<qreal> oldMarginsBefore = m_oldMargins;
314 m_oldMargins = m_marginsObject ? m_marginsObject->margins() : QList<qreal>();
315
316 if (oldMarginsBefore != m_oldMargins) {
317 m_marginsObject->update();
318 }
319 }
320
321private:
322 QList<qreal> &m_oldMargins;
323 FrameSvgItemMargins *const m_marginsObject;
324};
325
326void FrameSvgItem::setImagePath(const QString &path)
327{
328 if (m_frameSvg->imagePath() == path) {
329 return;
330 }
331
332 CheckMarginsChange checkMargins(m_oldMargins, m_margins);
333 CheckMarginsChange checkFixedMargins(m_oldFixedMargins, m_fixedMargins);
334 CheckMarginsChange checkInsetMargins(m_oldInsetMargins, m_insetMargins);
335
336 updateDevicePixelRatio();
337 m_frameSvg->setImagePath(path);
338
339 if (implicitWidth() <= 0) {
340 setImplicitWidth(m_frameSvg->marginSize(edge: KSvg::FrameSvg::LeftMargin) + m_frameSvg->marginSize(edge: KSvg::FrameSvg::RightMargin));
341 }
342
343 if (implicitHeight() <= 0) {
344 setImplicitHeight(m_frameSvg->marginSize(edge: KSvg::FrameSvg::TopMargin) + m_frameSvg->marginSize(edge: KSvg::FrameSvg::BottomMargin));
345 }
346
347 Q_EMIT imagePathChanged();
348
349 if (isComponentComplete()) {
350 applyPrefixes();
351
352 m_frameSvg->resizeFrame(size: size());
353 m_textureChanged = true;
354 update();
355 }
356}
357
358QString FrameSvgItem::imagePath() const
359{
360 return m_frameSvg->imagePath();
361}
362
363void FrameSvgItem::setPrefix(const QVariant &prefixes)
364{
365 QStringList prefixList;
366 // is this a simple string?
367 if (prefixes.canConvert<QString>()) {
368 prefixList << prefixes.toString();
369 } else if (prefixes.canConvert<QStringList>()) {
370 prefixList = prefixes.toStringList();
371 }
372
373 if (m_prefixes == prefixList) {
374 return;
375 }
376
377 CheckMarginsChange checkMargins(m_oldMargins, m_margins);
378 CheckMarginsChange checkFixedMargins(m_oldFixedMargins, m_fixedMargins);
379 CheckMarginsChange checkInsetMargins(m_oldInsetMargins, m_insetMargins);
380
381 m_prefixes = prefixList;
382 applyPrefixes();
383
384 if (implicitWidth() <= 0) {
385 setImplicitWidth(m_frameSvg->marginSize(edge: KSvg::FrameSvg::LeftMargin) + m_frameSvg->marginSize(edge: KSvg::FrameSvg::RightMargin));
386 }
387
388 if (implicitHeight() <= 0) {
389 setImplicitHeight(m_frameSvg->marginSize(edge: KSvg::FrameSvg::TopMargin) + m_frameSvg->marginSize(edge: KSvg::FrameSvg::BottomMargin));
390 }
391
392 Q_EMIT prefixChanged();
393
394 if (isComponentComplete()) {
395 m_frameSvg->resizeFrame(size: QSizeF(width(), height()));
396 m_textureChanged = true;
397 update();
398 }
399}
400
401QVariant FrameSvgItem::prefix() const
402{
403 return m_prefixes;
404}
405
406QString FrameSvgItem::usedPrefix() const
407{
408 return m_frameSvg->prefix();
409}
410
411FrameSvgItemMargins *FrameSvgItem::margins()
412{
413 if (!m_margins) {
414 m_margins = new FrameSvgItemMargins(m_frameSvg, this);
415 }
416 return m_margins;
417}
418
419FrameSvgItemMargins *FrameSvgItem::fixedMargins()
420{
421 if (!m_fixedMargins) {
422 m_fixedMargins = new FrameSvgItemMargins(m_frameSvg, this);
423 m_fixedMargins->setFixed(true);
424 }
425 return m_fixedMargins;
426}
427
428FrameSvgItemMargins *FrameSvgItem::inset()
429{
430 if (!m_insetMargins) {
431 m_insetMargins = new FrameSvgItemMargins(m_frameSvg, this);
432 m_insetMargins->setInset(true);
433 }
434 return m_insetMargins;
435}
436
437bool FrameSvgItem::fromCurrentImageSet() const
438{
439 return m_frameSvg->fromCurrentImageSet();
440}
441
442void FrameSvgItem::setStatus(KSvg::Svg::Status status)
443{
444 m_frameSvg->setStatus(status);
445}
446
447KSvg::Svg::Status FrameSvgItem::status() const
448{
449 return m_frameSvg->status();
450}
451
452void FrameSvgItem::setEnabledBorders(const KSvg::FrameSvg::EnabledBorders borders)
453{
454 if (m_frameSvg->enabledBorders() == borders) {
455 return;
456 }
457
458 CheckMarginsChange checkMargins(m_oldMargins, m_margins);
459
460 m_frameSvg->setEnabledBorders(borders);
461 Q_EMIT enabledBordersChanged();
462 m_textureChanged = true;
463 update();
464}
465
466KSvg::FrameSvg::EnabledBorders FrameSvgItem::enabledBorders() const
467{
468 return m_frameSvg->enabledBorders();
469}
470
471void FrameSvgItem::setColorSet(KSvg::Svg::ColorSet colorSet)
472{
473 if (m_frameSvg->colorSet() == colorSet) {
474 return;
475 }
476
477 m_frameSvg->setColorSet(colorSet);
478 m_textureChanged = true;
479
480 update();
481}
482
483KSvg::Svg::ColorSet FrameSvgItem::colorSet() const
484{
485 return m_frameSvg->colorSet();
486}
487
488bool FrameSvgItem::hasElementPrefix(const QString &prefix) const
489{
490 return m_frameSvg->hasElementPrefix(prefix);
491}
492
493bool FrameSvgItem::hasElement(const QString &elementName) const
494{
495 return m_frameSvg->hasElement(elementId: elementName);
496}
497
498QRegion FrameSvgItem::mask() const
499{
500 return m_frameSvg->mask();
501}
502
503int FrameSvgItem::minimumDrawingHeight() const
504{
505 return m_frameSvg->minimumDrawingHeight();
506}
507
508int FrameSvgItem::minimumDrawingWidth() const
509{
510 return m_frameSvg->minimumDrawingWidth();
511}
512
513void FrameSvgItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
514{
515 const bool isComponentComplete = this->isComponentComplete();
516 if (isComponentComplete) {
517 m_frameSvg->resizeFrame(size: newGeometry.size());
518 m_sizeChanged = true;
519 }
520
521 QQuickItem::geometryChange(newGeometry, oldGeometry);
522
523 // the above only triggers updatePaintNode, so we have to inform subscribers
524 // about the potential change of the mask explicitly here
525 if (isComponentComplete) {
526 Q_EMIT maskChanged();
527 }
528}
529
530void FrameSvgItem::doUpdate()
531{
532 if (m_frameSvg->isRepaintBlocked()) {
533 return;
534 }
535
536 CheckMarginsChange checkMargins(m_oldMargins, m_margins);
537 CheckMarginsChange checkFixedMargins(m_oldFixedMargins, m_fixedMargins);
538 CheckMarginsChange checkInsetMargins(m_oldInsetMargins, m_insetMargins);
539
540 // if the theme changed, the available prefix may have changed as well
541 applyPrefixes();
542
543 if (implicitWidth() <= 0) {
544 setImplicitWidth(m_frameSvg->marginSize(edge: KSvg::FrameSvg::LeftMargin) + m_frameSvg->marginSize(edge: KSvg::FrameSvg::RightMargin));
545 }
546
547 if (implicitHeight() <= 0) {
548 setImplicitHeight(m_frameSvg->marginSize(edge: KSvg::FrameSvg::TopMargin) + m_frameSvg->marginSize(edge: KSvg::FrameSvg::BottomMargin));
549 }
550
551 QString prefix = m_frameSvg->actualPrefix();
552 bool hasOverlay = (!prefix.startsWith(s: QLatin1String("mask-")) //
553 && m_frameSvg->hasElement(elementId: prefix % QLatin1String("overlay")));
554 bool hasComposeOverBorder = m_frameSvg->hasElement(elementId: prefix % QLatin1String("hint-compose-over-border"))
555 && m_frameSvg->hasElement(elementId: QLatin1String("mask-") % prefix % QLatin1String("center"));
556 m_fastPath = !hasOverlay && !hasComposeOverBorder;
557
558 // Software rendering (at time of writing Qt5.10) doesn't seem to like our
559 // tiling/stretching in the 9-tiles.
560 // Also when using QPainter it's arguably faster to create and cache pixmaps
561 // of the whole frame, which is what the slow path does
562 if (QQuickWindow::sceneGraphBackend() == QLatin1String("software")) {
563 m_fastPath = false;
564 }
565 m_textureChanged = true;
566
567 update();
568
569 Q_EMIT maskChanged();
570 Q_EMIT repaintNeeded();
571}
572
573KSvg::FrameSvg *FrameSvgItem::frameSvg() const
574{
575 return m_frameSvg;
576}
577
578QSGNode *FrameSvgItem::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
579{
580 if (!window() || !m_frameSvg //
581 || (!m_frameSvg->hasElementPrefix(prefix: m_frameSvg->actualPrefix()) //
582 && !m_frameSvg->hasElementPrefix(prefix: m_frameSvg->prefix()))) {
583 delete oldNode;
584 return nullptr;
585 }
586
587 const QSGTexture::Filtering filtering = smooth() ? QSGTexture::Linear : QSGTexture::Nearest;
588
589 if (m_fastPath) {
590 if (m_textureChanged) {
591 delete oldNode;
592 oldNode = nullptr;
593 }
594
595 if (!oldNode) {
596 QString prefix = m_frameSvg->actualPrefix();
597 oldNode = new FrameNode(prefix, m_frameSvg);
598
599 bool tileCenter = (m_frameSvg->hasElement(QStringLiteral("hint-tile-center")) //
600 || m_frameSvg->hasElement(elementId: prefix % QLatin1String("hint-tile-center")));
601 bool stretchBorders = (m_frameSvg->hasElement(QStringLiteral("hint-stretch-borders")) //
602 || m_frameSvg->hasElement(elementId: prefix % QLatin1String("hint-stretch-borders")));
603 FrameItemNode::FitMode borderFitMode = stretchBorders ? FrameItemNode::Stretch : FrameItemNode::Tile;
604 FrameItemNode::FitMode centerFitMode = tileCenter ? FrameItemNode::Tile : FrameItemNode::Stretch;
605
606 new FrameItemNode(this, FrameSvg::NoBorder, centerFitMode, oldNode);
607 if (enabledBorders() & (FrameSvg::TopBorder | FrameSvg::LeftBorder)) {
608 new FrameItemNode(this, FrameSvg::TopBorder | FrameSvg::LeftBorder, FrameItemNode::FastStretch, oldNode);
609 }
610 if (enabledBorders() & (FrameSvg::TopBorder | FrameSvg::RightBorder)) {
611 new FrameItemNode(this, FrameSvg::TopBorder | FrameSvg::RightBorder, FrameItemNode::FastStretch, oldNode);
612 }
613 if (enabledBorders() & FrameSvg::TopBorder) {
614 new FrameItemNode(this, FrameSvg::TopBorder, borderFitMode, oldNode);
615 }
616 if (enabledBorders() & FrameSvg::BottomBorder) {
617 new FrameItemNode(this, FrameSvg::BottomBorder, borderFitMode, oldNode);
618 }
619 if (enabledBorders() & (FrameSvg::BottomBorder | FrameSvg::LeftBorder)) {
620 new FrameItemNode(this, FrameSvg::BottomBorder | FrameSvg::LeftBorder, FrameItemNode::FastStretch, oldNode);
621 }
622 if (enabledBorders() & (FrameSvg::BottomBorder | FrameSvg::RightBorder)) {
623 new FrameItemNode(this, FrameSvg::BottomBorder | FrameSvg::RightBorder, FrameItemNode::FastStretch, oldNode);
624 }
625 if (enabledBorders() & FrameSvg::LeftBorder) {
626 new FrameItemNode(this, FrameSvg::LeftBorder, borderFitMode, oldNode);
627 }
628 if (enabledBorders() & FrameSvg::RightBorder) {
629 new FrameItemNode(this, FrameSvg::RightBorder, borderFitMode, oldNode);
630 }
631
632 m_sizeChanged = true;
633 m_textureChanged = false;
634 }
635
636 QSGNode *node = oldNode->firstChild();
637 while (node) {
638 static_cast<FrameItemNode *>(node)->setFiltering(filtering);
639 node = node->nextSibling();
640 }
641
642 if (m_sizeChanged) {
643 FrameNode *frameNode = static_cast<FrameNode *>(oldNode);
644 QSize frameSize(width(), height());
645 QRect geometry = frameNode->contentsRect(size: frameSize);
646 QSGNode *node = oldNode->firstChild();
647 while (node) {
648 static_cast<FrameItemNode *>(node)->reposition(frameGeometry: geometry, fullSize&: frameSize);
649 node = node->nextSibling();
650 }
651
652 m_sizeChanged = false;
653 }
654 } else {
655 ManagedTextureNode *textureNode = dynamic_cast<ManagedTextureNode *>(oldNode);
656 if (!textureNode) {
657 delete oldNode;
658 textureNode = new ManagedTextureNode;
659 m_textureChanged = true; // force updating the texture on our newly created node
660 oldNode = textureNode;
661 }
662 textureNode->setFiltering(filtering);
663
664 if ((m_textureChanged || m_sizeChanged) || textureNode->texture()->textureSize() != m_frameSvg->size()) {
665 QImage image = m_frameSvg->framePixmap().toImage();
666 textureNode->setTexture(s_cache->loadTexture(window: window(), image));
667 textureNode->setRect(x: 0, y: 0, w: width(), h: height());
668
669 m_textureChanged = false;
670 m_sizeChanged = false;
671 }
672 }
673
674 return oldNode;
675}
676
677void FrameSvgItem::classBegin()
678{
679 QQuickItem::classBegin();
680 m_frameSvg->setRepaintBlocked(true);
681}
682
683void FrameSvgItem::componentComplete()
684{
685 m_kirigamiTheme = qobject_cast<Kirigami::Platform::PlatformTheme *>(object: qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(obj: this, create: true));
686 if (!m_kirigamiTheme) {
687 qCWarning(LOG_KSVGQML) << "no theme!" << qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(obj: this, create: true) << this;
688 return;
689 }
690
691 auto checkApplyTheme = [this]() {
692 if (!m_frameSvg->imageSet()->filePath(QStringLiteral("colors")).isEmpty()) {
693 m_frameSvg->clearCache();
694 m_frameSvg->clearColorOverrides();
695 }
696 };
697 auto applyTheme = [this]() {
698 if (!m_frameSvg->imageSet()->filePath(QStringLiteral("colors")).isEmpty()) {
699 m_frameSvg->clearCache();
700 m_frameSvg->clearColorOverrides();
701
702 return;
703 }
704 m_frameSvg->setColor(colorName: Svg::Text, color: m_kirigamiTheme->textColor());
705 m_frameSvg->setColor(colorName: Svg::Background, color: m_kirigamiTheme->backgroundColor());
706 m_frameSvg->setColor(colorName: Svg::Highlight, color: m_kirigamiTheme->highlightColor());
707 m_frameSvg->setColor(colorName: Svg::HighlightedText, color: m_kirigamiTheme->highlightedTextColor());
708 m_frameSvg->setColor(colorName: Svg::PositiveText, color: m_kirigamiTheme->positiveTextColor());
709 m_frameSvg->setColor(colorName: Svg::NeutralText, color: m_kirigamiTheme->neutralTextColor());
710 m_frameSvg->setColor(colorName: Svg::NegativeText, color: m_kirigamiTheme->negativeTextColor());
711 };
712 applyTheme();
713 connect(sender: m_kirigamiTheme, signal: &Kirigami::Platform::PlatformTheme::colorsChanged, context: this, slot&: applyTheme);
714 connect(sender: m_frameSvg->imageSet(), signal: &ImageSet::imageSetChanged, context: this, slot&: checkApplyTheme);
715 connect(sender: m_frameSvg, signal: &Svg::imageSetChanged, context: this, slot&: checkApplyTheme);
716
717 CheckMarginsChange checkMargins(m_oldMargins, m_margins);
718 CheckMarginsChange checkFixedMargins(m_oldFixedMargins, m_fixedMargins);
719 CheckMarginsChange checkInsetMargins(m_oldInsetMargins, m_insetMargins);
720
721 QQuickItem::componentComplete();
722 m_frameSvg->resizeFrame(size: size());
723 m_frameSvg->setRepaintBlocked(false);
724 m_textureChanged = true;
725}
726
727void FrameSvgItem::updateDevicePixelRatio()
728{
729 // devicepixelratio is always set integer in the svg, so needs at least 192dpi to double up.
730 //(it needs to be integer to have lines contained inside a svg piece to keep being pixel aligned)
731 const auto newDevicePixelRatio = std::max<qreal>(a: 1.0, b: floor(x: window() ? window()->devicePixelRatio() : qApp->devicePixelRatio()));
732
733 if (newDevicePixelRatio != m_frameSvg->devicePixelRatio()) {
734 m_frameSvg->setDevicePixelRatio(newDevicePixelRatio);
735 m_textureChanged = true;
736 }
737}
738
739void FrameSvgItem::applyPrefixes()
740{
741 if (m_frameSvg->imagePath().isEmpty()) {
742 return;
743 }
744
745 const QString oldPrefix = m_frameSvg->prefix();
746
747 if (m_prefixes.isEmpty()) {
748 m_frameSvg->setElementPrefix(QString());
749 if (oldPrefix != m_frameSvg->prefix()) {
750 Q_EMIT usedPrefixChanged();
751 }
752 return;
753 }
754
755 bool found = false;
756 for (const QString &prefix : std::as_const(t&: m_prefixes)) {
757 if (m_frameSvg->hasElementPrefix(prefix)) {
758 m_frameSvg->setElementPrefix(prefix);
759 found = true;
760 break;
761 }
762 }
763 if (!found) {
764 // this setElementPrefix is done to keep the same behavior as before, when it was a simple string
765 m_frameSvg->setElementPrefix(m_prefixes.constLast());
766 }
767 if (oldPrefix != m_frameSvg->prefix()) {
768 Q_EMIT usedPrefixChanged();
769 }
770}
771
772void FrameSvgItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
773{
774 if (change == ItemSceneChange && value.window) {
775 updateDevicePixelRatio();
776 } else if (change == QQuickItem::ItemDevicePixelRatioHasChanged) {
777 updateDevicePixelRatio();
778 }
779
780 QQuickItem::itemChange(change, value);
781}
782
783} // KSvg namespace
784
785#include "moc_framesvgitem.cpp"
786

source code of ksvg/src/declarativeimports/framesvgitem.cpp