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 | |
28 | namespace KSvg |
29 | { |
30 | Q_GLOBAL_STATIC(ImageTexturesCache, s_cache) |
31 | |
32 | class FrameNode : public QSGNode |
33 | { |
34 | public: |
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 | |
63 | private: |
64 | int leftWidth; |
65 | int rightWidth; |
66 | int topHeight; |
67 | int bottomHeight; |
68 | }; |
69 | |
70 | class FrameItemNode : public ManagedTextureNode |
71 | { |
72 | public: |
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 | |
165 | private: |
166 | FrameSvgItem *m_frameSvg; |
167 | FrameSvg::EnabledBorders m_border; |
168 | QSize m_elementNativeSize; |
169 | FitMode m_fitMode; |
170 | }; |
171 | |
172 | FrameSvgItemMargins::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 | |
181 | qreal 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 | |
192 | qreal 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 | |
203 | qreal 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 | |
214 | qreal 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 | |
225 | qreal FrameSvgItemMargins::horizontal() const |
226 | { |
227 | return left() + right(); |
228 | } |
229 | |
230 | qreal FrameSvgItemMargins::vertical() const |
231 | { |
232 | return top() + bottom(); |
233 | } |
234 | |
235 | QList<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 | |
245 | void FrameSvgItemMargins::update() |
246 | { |
247 | Q_EMIT marginsChanged(); |
248 | } |
249 | |
250 | void FrameSvgItemMargins::setFixed(bool fixed) |
251 | { |
252 | if (fixed == m_fixed) { |
253 | return; |
254 | } |
255 | |
256 | m_fixed = fixed; |
257 | Q_EMIT marginsChanged(); |
258 | } |
259 | |
260 | bool FrameSvgItemMargins::isFixed() const |
261 | { |
262 | return m_fixed; |
263 | } |
264 | |
265 | void FrameSvgItemMargins::setInset(bool inset) |
266 | { |
267 | if (inset == m_inset) { |
268 | return; |
269 | } |
270 | |
271 | m_inset = inset; |
272 | Q_EMIT marginsChanged(); |
273 | } |
274 | |
275 | bool FrameSvgItemMargins::isInset() const |
276 | { |
277 | return m_inset; |
278 | } |
279 | |
280 | FrameSvgItem::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 | |
298 | FrameSvgItem::~FrameSvgItem() |
299 | { |
300 | } |
301 | |
302 | class CheckMarginsChange |
303 | { |
304 | public: |
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 | |
321 | private: |
322 | QList<qreal> &m_oldMargins; |
323 | FrameSvgItemMargins *const m_marginsObject; |
324 | }; |
325 | |
326 | void 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 | |
358 | QString FrameSvgItem::imagePath() const |
359 | { |
360 | return m_frameSvg->imagePath(); |
361 | } |
362 | |
363 | void 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 | |
401 | QVariant FrameSvgItem::prefix() const |
402 | { |
403 | return m_prefixes; |
404 | } |
405 | |
406 | QString FrameSvgItem::usedPrefix() const |
407 | { |
408 | return m_frameSvg->prefix(); |
409 | } |
410 | |
411 | FrameSvgItemMargins *FrameSvgItem::margins() |
412 | { |
413 | if (!m_margins) { |
414 | m_margins = new FrameSvgItemMargins(m_frameSvg, this); |
415 | } |
416 | return m_margins; |
417 | } |
418 | |
419 | FrameSvgItemMargins *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 | |
428 | FrameSvgItemMargins *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 | |
437 | bool FrameSvgItem::fromCurrentImageSet() const |
438 | { |
439 | return m_frameSvg->fromCurrentImageSet(); |
440 | } |
441 | |
442 | void FrameSvgItem::setStatus(KSvg::Svg::Status status) |
443 | { |
444 | m_frameSvg->setStatus(status); |
445 | } |
446 | |
447 | KSvg::Svg::Status FrameSvgItem::status() const |
448 | { |
449 | return m_frameSvg->status(); |
450 | } |
451 | |
452 | void 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 | |
466 | KSvg::FrameSvg::EnabledBorders FrameSvgItem::enabledBorders() const |
467 | { |
468 | return m_frameSvg->enabledBorders(); |
469 | } |
470 | |
471 | void 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 | |
483 | KSvg::Svg::ColorSet FrameSvgItem::colorSet() const |
484 | { |
485 | return m_frameSvg->colorSet(); |
486 | } |
487 | |
488 | bool FrameSvgItem::hasElementPrefix(const QString &prefix) const |
489 | { |
490 | return m_frameSvg->hasElementPrefix(prefix); |
491 | } |
492 | |
493 | bool FrameSvgItem::hasElement(const QString &elementName) const |
494 | { |
495 | return m_frameSvg->hasElement(elementId: elementName); |
496 | } |
497 | |
498 | QRegion FrameSvgItem::mask() const |
499 | { |
500 | return m_frameSvg->mask(); |
501 | } |
502 | |
503 | int FrameSvgItem::minimumDrawingHeight() const |
504 | { |
505 | return m_frameSvg->minimumDrawingHeight(); |
506 | } |
507 | |
508 | int FrameSvgItem::minimumDrawingWidth() const |
509 | { |
510 | return m_frameSvg->minimumDrawingWidth(); |
511 | } |
512 | |
513 | void 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 | |
530 | void 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 | |
573 | KSvg::FrameSvg *FrameSvgItem::frameSvg() const |
574 | { |
575 | return m_frameSvg; |
576 | } |
577 | |
578 | QSGNode *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 | |
677 | void FrameSvgItem::classBegin() |
678 | { |
679 | QQuickItem::classBegin(); |
680 | m_frameSvg->setRepaintBlocked(true); |
681 | } |
682 | |
683 | void 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 | |
727 | void 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 | |
739 | void 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 | |
772 | void 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 | |