1/*
2 * SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
3 * SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
8#include "icon.h"
9#include "scenegraph/managedtexturenode.h"
10
11#include "platform/platformtheme.h"
12#include "platform/units.h"
13
14#include "loggingcategory.h"
15#include <QBitmap>
16#include <QDebug>
17#include <QGuiApplication>
18#include <QIcon>
19#include <QNetworkReply>
20#include <QPainter>
21#include <QPropertyAnimation>
22#include <QQuickImageProvider>
23#include <QQuickWindow>
24#include <QSGSimpleTextureNode>
25#include <QSGTexture>
26#include <QScreen>
27#include <cstdlib>
28
29Q_GLOBAL_STATIC(ImageTexturesCache, s_iconImageCache)
30
31Icon::Icon(QQuickItem *parent)
32 : QQuickItem(parent)
33 , m_active(false)
34 , m_selected(false)
35 , m_isMask(false)
36{
37 setFlag(flag: ItemHasContents, enabled: true);
38 // Using 32 because Icon used to redefine implicitWidth and implicitHeight and hardcode them to 32
39 setImplicitSize(32, 32);
40
41 connect(sender: this, signal: &QQuickItem::smoothChanged, context: this, slot: &QQuickItem::polish);
42 connect(sender: this, signal: &QQuickItem::enabledChanged, context: this, slot: [this]() {
43 m_allowNextAnimation = true;
44 polish();
45 });
46}
47
48Icon::~Icon()
49{
50}
51
52void Icon::componentComplete()
53{
54 QQuickItem::componentComplete();
55
56 QQmlEngine *engine = qmlEngine(this);
57 Q_ASSERT(engine);
58 m_units = engine->singletonInstance<Kirigami::Platform::Units *>(uri: "org.kde.kirigami.platform", typeName: "Units");
59 Q_ASSERT(m_units);
60 m_animation = new QPropertyAnimation(this);
61 connect(sender: m_animation, signal: &QPropertyAnimation::valueChanged, context: this, slot: &Icon::valueChanged);
62 connect(sender: m_animation, signal: &QPropertyAnimation::finished, context: this, slot: [this]() {
63 m_oldIcon = QImage();
64 m_textureChanged = true;
65 update();
66 });
67 m_animation->setTargetObject(this);
68 m_animation->setEasingCurve(QEasingCurve::InOutCubic);
69 m_animation->setDuration(m_units->longDuration());
70 connect(sender: m_units, signal: &Kirigami::Platform::Units::longDurationChanged, context: m_animation, slot: [this]() {
71 m_animation->setDuration(m_units->longDuration());
72 });
73 updatePaintedGeometry();
74}
75
76void Icon::setSource(const QVariant &icon)
77{
78 if (m_source == icon) {
79 return;
80 }
81 m_source = icon;
82
83 if (!m_theme) {
84 m_theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(obj: this, create: true));
85 Q_ASSERT(m_theme);
86
87 connect(sender: m_theme, signal: &Kirigami::Platform::PlatformTheme::colorsChanged, context: this, slot: &QQuickItem::polish);
88 }
89
90 if (m_networkReply) {
91 // if there was a network query going on, interrupt it
92 m_networkReply->close();
93 }
94 m_loadedImage = QImage();
95 setStatus(Loading);
96
97 polish();
98 Q_EMIT sourceChanged();
99 Q_EMIT validChanged();
100}
101
102QVariant Icon::source() const
103{
104 return m_source;
105}
106
107void Icon::setActive(const bool active)
108{
109 if (active == m_active) {
110 return;
111 }
112 m_active = active;
113 if (isComponentComplete()) {
114 m_allowNextAnimation = true;
115 }
116 polish();
117 Q_EMIT activeChanged();
118}
119
120bool Icon::active() const
121{
122 return m_active;
123}
124
125bool Icon::valid() const
126{
127 // TODO: should this be return m_status == Ready?
128 // Consider an empty URL invalid, even though isNull() will say false
129 if (m_source.canConvert<QUrl>() && m_source.toUrl().isEmpty()) {
130 return false;
131 }
132
133 return !m_source.isNull();
134}
135
136void Icon::setSelected(const bool selected)
137{
138 if (selected == m_selected) {
139 return;
140 }
141 m_selected = selected;
142 polish();
143 Q_EMIT selectedChanged();
144}
145
146bool Icon::selected() const
147{
148 return m_selected;
149}
150
151void Icon::setIsMask(bool mask)
152{
153 if (m_isMask == mask) {
154 return;
155 }
156
157 m_isMask = mask;
158 polish();
159 Q_EMIT isMaskChanged();
160}
161
162bool Icon::isMask() const
163{
164 return m_isMask;
165}
166
167void Icon::setColor(const QColor &color)
168{
169 if (m_color == color) {
170 return;
171 }
172
173 m_color = color;
174 polish();
175 Q_EMIT colorChanged();
176}
177
178QColor Icon::color() const
179{
180 return m_color;
181}
182
183QSGNode *Icon::createSubtree(qreal initialOpacity)
184{
185 auto opacityNode = new QSGOpacityNode{};
186 opacityNode->setFlag(QSGNode::OwnedByParent, true);
187 opacityNode->setOpacity(initialOpacity);
188
189 auto *mNode = new ManagedTextureNode;
190
191 mNode->setTexture(s_iconImageCache->loadTexture(window: window(), image: m_icon, options: QQuickWindow::TextureCanUseAtlas));
192
193 opacityNode->appendChildNode(node: mNode);
194
195 return opacityNode;
196}
197
198void Icon::updateSubtree(QSGNode *node, qreal opacity)
199{
200 auto opacityNode = static_cast<QSGOpacityNode *>(node);
201 opacityNode->setOpacity(opacity);
202
203 auto textureNode = static_cast<ManagedTextureNode *>(opacityNode->firstChild());
204 textureNode->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
205}
206
207QSGNode *Icon::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData * /*data*/)
208{
209 if (m_source.isNull() || qFuzzyIsNull(d: width()) || qFuzzyIsNull(d: height())) {
210 delete node;
211 return Q_NULLPTR;
212 }
213
214 if (!node) {
215 node = new QSGNode{};
216 }
217
218 if (m_animation && m_animation->state() == QAbstractAnimation::Running) {
219 if (node->childCount() < 2) {
220 node->appendChildNode(node: createSubtree(initialOpacity: 0.0));
221 m_textureChanged = true;
222 }
223
224 // Rather than doing a perfect crossfade, first fade in the new texture
225 // then fade out the old texture. This is done to avoid the underlying
226 // color bleeding through when both textures are at ~0.5 opacity, which
227 // causes flickering if the two textures are very similar.
228 updateSubtree(node: node->firstChild(), opacity: 2.0 - m_animValue * 2.0);
229 updateSubtree(node: node->lastChild(), opacity: m_animValue * 2.0);
230 } else {
231 if (node->childCount() == 0) {
232 node->appendChildNode(node: createSubtree(initialOpacity: 1.0));
233 m_textureChanged = true;
234 }
235
236 if (node->childCount() > 1) {
237 auto toRemove = node->firstChild();
238 node->removeChildNode(node: toRemove);
239 delete toRemove;
240 }
241
242 updateSubtree(node: node->firstChild(), opacity: 1.0);
243 }
244
245 if (m_textureChanged) {
246 auto mNode = static_cast<ManagedTextureNode *>(node->lastChild()->firstChild());
247 mNode->setTexture(s_iconImageCache->loadTexture(window: window(), image: m_icon, options: QQuickWindow::TextureCanUseAtlas));
248 m_textureChanged = false;
249 m_sizeChanged = true;
250 }
251
252 if (m_sizeChanged) {
253 const QSizeF iconPixSize(m_icon.width() / m_devicePixelRatio, m_icon.height() / m_devicePixelRatio);
254 const QSizeF itemPixSize = QSizeF((size() * m_devicePixelRatio).toSize()) / m_devicePixelRatio;
255 QRectF nodeRect(QPoint(0, 0), itemPixSize);
256
257 if (itemPixSize.width() != 0 && itemPixSize.height() != 0) {
258 if (iconPixSize != itemPixSize) {
259 // At this point, the image will already be scaled, but we need to output it in
260 // the correct aspect ratio, painted centered in the viewport. So:
261 QRectF destination(QPointF(0, 0), QSizeF(m_icon.size()).scaled(s: m_paintedSize, mode: Qt::KeepAspectRatio));
262 destination.moveCenter(p: nodeRect.center());
263 destination.moveTopLeft(p: QPointF(destination.topLeft().toPoint() * m_devicePixelRatio) / m_devicePixelRatio);
264 nodeRect = destination;
265 }
266 }
267
268 // Adjust the final node on the pixel grid
269 QPointF globalPixelPos = mapToScene(point: nodeRect.topLeft()) * m_devicePixelRatio;
270 QPointF posAdjust = QPointF(globalPixelPos.x() - std::round(x: globalPixelPos.x()), globalPixelPos.y() - std::round(x: globalPixelPos.y()));
271 nodeRect.moveTopLeft(p: nodeRect.topLeft() - posAdjust);
272
273 for (int i = 0; i < node->childCount(); ++i) {
274 auto mNode = static_cast<ManagedTextureNode *>(node->childAtIndex(i)->firstChild());
275 mNode->setRect(nodeRect);
276 }
277
278 m_sizeChanged = false;
279 }
280
281 return node;
282}
283
284void Icon::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
285{
286 QQuickItem::geometryChange(newGeometry, oldGeometry);
287 if (newGeometry.size() != oldGeometry.size()) {
288 m_sizeChanged = true;
289 updatePaintedGeometry();
290 polish();
291 }
292}
293
294void Icon::handleRedirect(QNetworkReply *reply)
295{
296 QNetworkAccessManager *qnam = reply->manager();
297 if (reply->error() != QNetworkReply::NoError) {
298 return;
299 }
300 const QUrl possibleRedirectUrl = reply->attribute(code: QNetworkRequest::RedirectionTargetAttribute).toUrl();
301 if (!possibleRedirectUrl.isEmpty()) {
302 const QUrl redirectUrl = reply->url().resolved(relative: possibleRedirectUrl);
303 if (redirectUrl == reply->url()) {
304 // no infinite redirections thank you very much
305 reply->deleteLater();
306 return;
307 }
308 reply->deleteLater();
309 QNetworkRequest request(possibleRedirectUrl);
310 request.setAttribute(code: QNetworkRequest::CacheLoadControlAttribute, value: QNetworkRequest::PreferCache);
311 m_networkReply = qnam->get(request);
312 connect(sender: m_networkReply.data(), signal: &QNetworkReply::finished, context: this, slot: [this]() {
313 handleFinished(reply: m_networkReply);
314 });
315 }
316}
317
318void Icon::handleFinished(QNetworkReply *reply)
319{
320 if (!reply) {
321 return;
322 }
323
324 reply->deleteLater();
325 if (!reply->attribute(code: QNetworkRequest::RedirectionTargetAttribute).isNull()) {
326 handleRedirect(reply);
327 return;
328 }
329
330 m_loadedImage = QImage();
331
332 const QString filename = reply->url().fileName();
333 if (!m_loadedImage.load(device: reply, format: filename.mid(position: filename.indexOf(c: QLatin1Char('.'))).toLatin1().constData())) {
334 qCWarning(KirigamiLog) << "received broken image" << reply->url();
335
336 // broken image from data, inform the user of this with some useful broken-image thing...
337 m_loadedImage = iconPixmap(icon: QIcon::fromTheme(name: m_fallback));
338 }
339
340 polish();
341}
342
343void Icon::updatePolish()
344{
345 QQuickItem::updatePolish();
346
347 if (window()) {
348 m_devicePixelRatio = window()->effectiveDevicePixelRatio();
349 }
350
351 if (m_source.isNull()) {
352 setStatus(Ready);
353 updatePaintedGeometry();
354 update();
355 return;
356 }
357
358 const QSize itemSize(width(), height());
359 if (itemSize.width() != 0 && itemSize.height() != 0) {
360 const QSize size = itemSize;
361
362 if (m_animation) {
363 m_animation->stop();
364 m_oldIcon = m_icon;
365 }
366
367 switch (m_source.userType()) {
368 case QMetaType::QPixmap:
369 m_icon = m_source.value<QPixmap>().toImage();
370 break;
371 case QMetaType::QImage:
372 m_icon = m_source.value<QImage>();
373 break;
374 case QMetaType::QBitmap:
375 m_icon = m_source.value<QBitmap>().toImage();
376 break;
377 case QMetaType::QIcon: {
378 m_icon = iconPixmap(icon: m_source.value<QIcon>());
379 break;
380 }
381 case QMetaType::QUrl:
382 case QMetaType::QString:
383 m_icon = findIcon(size);
384 break;
385 case QMetaType::QBrush:
386 // todo: fill here too?
387 case QMetaType::QColor:
388 m_icon = QImage(size, QImage::Format_Alpha8);
389 m_icon.fill(color: m_source.value<QColor>());
390 break;
391 default:
392 break;
393 }
394
395 if (m_icon.isNull()) {
396 m_icon = QImage(size, QImage::Format_Alpha8);
397 m_icon.fill(color: Qt::transparent);
398 }
399
400 const QColor tintColor = //
401 !m_color.isValid() || m_color == Qt::transparent //
402 ? (m_selected ? m_theme->highlightedTextColor() : m_theme->textColor())
403 : m_color;
404
405 // TODO: initialize m_isMask with icon.isMask()
406 if (tintColor.alpha() > 0 && isMask()) {
407 QPainter p(&m_icon);
408 p.setCompositionMode(QPainter::CompositionMode_SourceIn);
409 p.fillRect(m_icon.rect(), color: tintColor);
410 p.end();
411 }
412 }
413
414 // don't animate initial setting
415 bool animated = (m_animated || m_allowNextAnimation) && !m_oldIcon.isNull() && !m_sizeChanged && !m_blockNextAnimation;
416
417 if (animated && m_animation) {
418 m_animValue = 0.0;
419 m_animation->setStartValue((qreal)0);
420 m_animation->setEndValue((qreal)1);
421 m_animation->start();
422 m_allowNextAnimation = false;
423 } else {
424 if (m_animation) {
425 m_animation->stop();
426 }
427 m_animValue = 1.0;
428 m_blockNextAnimation = false;
429 }
430 m_textureChanged = true;
431 updatePaintedGeometry();
432 update();
433}
434
435QImage Icon::findIcon(const QSize &size)
436{
437 QImage img;
438 QString iconSource = m_source.toString();
439
440 if (iconSource.startsWith(s: QLatin1String("image://"))) {
441 QUrl iconUrl(iconSource);
442 QString iconProviderId = iconUrl.host();
443 // QUrl path has the "/" prefix while iconId does not
444 QString iconId = iconUrl.path().remove(i: 0, len: 1);
445
446 QSize actualSize;
447 auto engine = qmlEngine(this);
448 if (!engine) {
449 return img;
450 }
451 QQuickImageProvider *imageProvider = dynamic_cast<QQuickImageProvider *>(engine->imageProvider(id: iconProviderId));
452 if (!imageProvider) {
453 return img;
454 }
455 switch (imageProvider->imageType()) {
456 case QQmlImageProviderBase::Image:
457 img = imageProvider->requestImage(id: iconId, size: &actualSize, requestedSize: size);
458 if (!img.isNull()) {
459 setStatus(Ready);
460 }
461 break;
462 case QQmlImageProviderBase::Pixmap:
463 img = imageProvider->requestPixmap(id: iconId, size: &actualSize, requestedSize: size).toImage();
464 if (!img.isNull()) {
465 setStatus(Ready);
466 }
467 break;
468 case QQmlImageProviderBase::ImageResponse: {
469 if (!m_loadedImage.isNull()) {
470 setStatus(Ready);
471 return m_loadedImage.scaled(s: size, aspectMode: Qt::KeepAspectRatio, mode: smooth() ? Qt::SmoothTransformation : Qt::FastTransformation);
472 }
473 QQuickAsyncImageProvider *provider = dynamic_cast<QQuickAsyncImageProvider *>(imageProvider);
474 auto response = provider->requestImageResponse(id: iconId, requestedSize: size);
475 connect(sender: response, signal: &QQuickImageResponse::finished, context: this, slot: [iconId, response, this]() {
476 if (response->errorString().isEmpty()) {
477 QQuickTextureFactory *textureFactory = response->textureFactory();
478 if (textureFactory) {
479 m_loadedImage = textureFactory->image();
480 delete textureFactory;
481 }
482 if (m_loadedImage.isNull()) {
483 // broken image from data, inform the user of this with some useful broken-image thing...
484 m_loadedImage = iconPixmap(icon: QIcon::fromTheme(name: m_fallback));
485 setStatus(Error);
486 } else {
487 setStatus(Ready);
488 }
489 polish();
490 }
491 response->deleteLater();
492 });
493 // Temporary icon while we wait for the real image to load...
494 img = iconPixmap(icon: QIcon::fromTheme(name: m_placeholder));
495 break;
496 }
497 case QQmlImageProviderBase::Texture: {
498 QQuickTextureFactory *textureFactory = imageProvider->requestTexture(id: iconId, size: &actualSize, requestedSize: size);
499 if (textureFactory) {
500 img = textureFactory->image();
501 }
502 if (img.isNull()) {
503 // broken image from data, or the texture factory wasn't healthy, inform the user of this with some useful broken-image thing...
504 img = iconPixmap(icon: QIcon::fromTheme(name: m_fallback));
505 setStatus(Error);
506 } else {
507 setStatus(Ready);
508 }
509 break;
510 }
511 case QQmlImageProviderBase::Invalid:
512 // will have to investigate this more
513 setStatus(Error);
514 break;
515 }
516 } else if (iconSource.startsWith(s: QLatin1String("http://")) || iconSource.startsWith(s: QLatin1String("https://"))) {
517 if (!m_loadedImage.isNull()) {
518 setStatus(Ready);
519 return m_loadedImage.scaled(s: size, aspectMode: Qt::KeepAspectRatio, mode: smooth() ? Qt::SmoothTransformation : Qt::FastTransformation);
520 }
521 const auto url = m_source.toUrl();
522 QQmlEngine *engine = qmlEngine(this);
523 QNetworkAccessManager *qnam;
524 if (engine && (qnam = engine->networkAccessManager()) && (!m_networkReply || m_networkReply->url() != url)) {
525 QNetworkRequest request(url);
526 request.setAttribute(code: QNetworkRequest::CacheLoadControlAttribute, value: QNetworkRequest::PreferCache);
527 m_networkReply = qnam->get(request);
528 connect(sender: m_networkReply.data(), signal: &QNetworkReply::finished, context: this, slot: [this]() {
529 handleFinished(reply: m_networkReply);
530 });
531 }
532 // Temporary icon while we wait for the real image to load...
533 img = iconPixmap(icon: QIcon::fromTheme(name: m_placeholder));
534 } else {
535 if (iconSource.startsWith(s: QLatin1String("qrc:/"))) {
536 iconSource = iconSource.mid(position: 3);
537 } else if (iconSource.startsWith(s: QLatin1String("file:/"))) {
538 iconSource = QUrl(iconSource).path();
539 }
540
541 QIcon icon;
542 const bool isPath = iconSource.contains(s: QLatin1String("/"));
543 if (isPath) {
544 icon = QIcon(iconSource);
545 } else {
546 const QColor tintColor =
547 !m_color.isValid() || m_color == Qt::transparent ? (m_selected ? m_theme->highlightedTextColor() : m_theme->textColor()) : m_color;
548 icon = m_theme->iconFromTheme(name: iconSource, customColor: tintColor);
549 }
550 if (!icon.isNull()) {
551 img = iconPixmap(icon);
552 setStatus(Ready);
553 }
554 }
555
556 if (!iconSource.isEmpty() && img.isNull()) {
557 setStatus(Error);
558 img = iconPixmap(icon: QIcon::fromTheme(name: m_fallback));
559 }
560 return img;
561}
562
563QIcon::Mode Icon::iconMode() const
564{
565 if (!isEnabled()) {
566 return QIcon::Disabled;
567 } else if (m_selected) {
568 return QIcon::Selected;
569 } else if (m_active) {
570 return QIcon::Active;
571 }
572 return QIcon::Normal;
573}
574
575QString Icon::fallback() const
576{
577 return m_fallback;
578}
579
580void Icon::setFallback(const QString &fallback)
581{
582 if (m_fallback != fallback) {
583 m_fallback = fallback;
584 Q_EMIT fallbackChanged(fallback);
585 }
586}
587
588QString Icon::placeholder() const
589{
590 return m_placeholder;
591}
592
593void Icon::setPlaceholder(const QString &placeholder)
594{
595 if (m_placeholder != placeholder) {
596 m_placeholder = placeholder;
597 Q_EMIT placeholderChanged(placeholder);
598 }
599}
600
601void Icon::setStatus(Status status)
602{
603 if (status == m_status) {
604 return;
605 }
606
607 m_status = status;
608 Q_EMIT statusChanged();
609}
610
611Icon::Status Icon::status() const
612{
613 return m_status;
614}
615
616qreal Icon::paintedWidth() const
617{
618 return std::round(x: m_paintedSize.width());
619}
620
621qreal Icon::paintedHeight() const
622{
623 return std::round(x: m_paintedSize.height());
624}
625
626QSize Icon::iconSizeHint() const
627{
628 if (!m_roundToIconSize) {
629 return QSize(width(), height());
630 } else if (m_units) {
631 return QSize(m_units->iconSizes()->roundedIconSize(size: std::min(a: width(), b: height())), m_units->iconSizes()->roundedIconSize(size: std::min(a: width(), b: height())));
632 } else {
633 return QSize(std::min(a: width(), b: height()), std::min(a: width(), b: height()));
634 }
635}
636
637QImage Icon::iconPixmap(const QIcon &icon) const
638{
639 const QSize actualSize = icon.actualSize(size: iconSizeHint());
640 return icon.pixmap(size: actualSize, devicePixelRatio: m_devicePixelRatio, mode: iconMode(), state: QIcon::On).toImage();
641}
642
643void Icon::updatePaintedGeometry()
644{
645 QSizeF newSize;
646 if (!m_icon.width() || !m_icon.height()) {
647 newSize = {0, 0};
648 } else {
649 qreal roundedWidth = m_units ? m_units->iconSizes()->roundedIconSize(size: std::min(a: width(), b: height())) : 32;
650 roundedWidth = std::round(x: roundedWidth * m_devicePixelRatio) / m_devicePixelRatio;
651
652 if (QSizeF roundedSize(roundedWidth, roundedWidth); size() == roundedSize) {
653 m_paintedSize = roundedSize;
654 m_textureChanged = true;
655 update();
656 Q_EMIT paintedAreaChanged();
657 return;
658 }
659 if (m_roundToIconSize && m_units) {
660 if (m_icon.width() > m_icon.height()) {
661 newSize = QSizeF(roundedWidth, m_icon.height() * (roundedWidth / static_cast<qreal>(m_icon.width())));
662 } else {
663 newSize = QSizeF(roundedWidth, roundedWidth);
664 }
665 } else {
666 const QSizeF iconPixSize(m_icon.width() / m_devicePixelRatio, m_icon.height() / m_devicePixelRatio);
667
668 const qreal w = widthValid() ? width() : iconPixSize.width();
669 const qreal widthScale = w / iconPixSize.width();
670 const qreal h = heightValid() ? height() : iconPixSize.height();
671 const qreal heightScale = h / iconPixSize.height();
672
673 if (widthScale <= heightScale) {
674 newSize = QSizeF(w, widthScale * iconPixSize.height());
675 } else if (heightScale < widthScale) {
676 newSize = QSizeF(heightScale * iconPixSize.width(), h);
677 }
678 }
679 }
680 if (newSize != m_paintedSize) {
681 m_paintedSize = newSize;
682 m_textureChanged = true;
683 update();
684 Q_EMIT paintedAreaChanged();
685 }
686}
687
688bool Icon::isAnimated() const
689{
690 return m_animated;
691}
692
693void Icon::setAnimated(bool animated)
694{
695 if (m_animated == animated) {
696 return;
697 }
698
699 m_animated = animated;
700 Q_EMIT animatedChanged();
701}
702
703bool Icon::roundToIconSize() const
704{
705 return m_roundToIconSize;
706}
707
708void Icon::setRoundToIconSize(bool roundToIconSize)
709{
710 if (m_roundToIconSize == roundToIconSize) {
711 return;
712 }
713
714 const QSizeF oldPaintedSize = m_paintedSize;
715
716 m_roundToIconSize = roundToIconSize;
717 Q_EMIT roundToIconSizeChanged();
718
719 updatePaintedGeometry();
720 if (oldPaintedSize != m_paintedSize) {
721 Q_EMIT paintedAreaChanged();
722 m_textureChanged = true;
723 update();
724 }
725}
726
727void Icon::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
728{
729 if (change == QQuickItem::ItemDevicePixelRatioHasChanged) {
730 m_blockNextAnimation = true;
731 if (window()) {
732 m_devicePixelRatio = window()->effectiveDevicePixelRatio();
733 }
734 polish();
735 } else if (change == QQuickItem::ItemSceneChange) {
736 if (m_window) {
737 disconnect(sender: m_window.data(), signal: &QWindow::visibleChanged, receiver: this, slot: &Icon::windowVisibleChanged);
738 }
739 m_window = value.window;
740 if (m_window) {
741 connect(sender: m_window.data(), signal: &QWindow::visibleChanged, context: this, slot: &Icon::windowVisibleChanged);
742 m_devicePixelRatio = m_window->effectiveDevicePixelRatio();
743 }
744 } else if (change == ItemVisibleHasChanged && value.boolValue) {
745 m_blockNextAnimation = true;
746 }
747 QQuickItem::itemChange(change, value);
748}
749
750void Icon::valueChanged(const QVariant &value)
751{
752 m_animValue = value.toReal();
753 update();
754}
755
756void Icon::windowVisibleChanged(bool visible)
757{
758 if (visible) {
759 m_blockNextAnimation = true;
760 }
761}
762
763#include "moc_icon.cpp"
764

source code of kirigami/src/icon.cpp