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

source code of kirigami/src/primitives/icon.cpp