File indexing completed on 2024-04-14 03:53:46
0001 /* 0002 * SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org> 0003 * SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "icon.h" 0009 #include "scenegraph/managedtexturenode.h" 0010 0011 #include "platform/platformtheme.h" 0012 #include "platform/units.h" 0013 0014 #include "loggingcategory.h" 0015 #include <QBitmap> 0016 #include <QDebug> 0017 #include <QGuiApplication> 0018 #include <QIcon> 0019 #include <QNetworkReply> 0020 #include <QPainter> 0021 #include <QPropertyAnimation> 0022 #include <QQuickImageProvider> 0023 #include <QQuickWindow> 0024 #include <QSGSimpleTextureNode> 0025 #include <QSGTexture> 0026 #include <QScreen> 0027 #include <cstdlib> 0028 0029 Q_GLOBAL_STATIC(ImageTexturesCache, s_iconImageCache) 0030 0031 Icon::Icon(QQuickItem *parent) 0032 : QQuickItem(parent) 0033 , m_active(false) 0034 , m_selected(false) 0035 , m_isMask(false) 0036 { 0037 setFlag(ItemHasContents, true); 0038 // Using 32 because Icon used to redefine implicitWidth and implicitHeight and hardcode them to 32 0039 setImplicitSize(32, 32); 0040 0041 connect(this, &QQuickItem::smoothChanged, this, &QQuickItem::polish); 0042 connect(this, &QQuickItem::enabledChanged, this, [this]() { 0043 m_allowNextAnimation = true; 0044 polish(); 0045 }); 0046 } 0047 0048 Icon::~Icon() 0049 { 0050 } 0051 0052 void Icon::componentComplete() 0053 { 0054 QQuickItem::componentComplete(); 0055 0056 QQmlEngine *engine = qmlEngine(this); 0057 Q_ASSERT(engine); 0058 m_units = engine->singletonInstance<Kirigami::Platform::Units *>("org.kde.kirigami.platform", "Units"); 0059 Q_ASSERT(m_units); 0060 m_animation = new QPropertyAnimation(this); 0061 connect(m_animation, &QPropertyAnimation::valueChanged, this, &Icon::valueChanged); 0062 connect(m_animation, &QPropertyAnimation::finished, this, [this]() { 0063 m_oldIcon = QImage(); 0064 m_textureChanged = true; 0065 update(); 0066 }); 0067 m_animation->setTargetObject(this); 0068 m_animation->setEasingCurve(QEasingCurve::InOutCubic); 0069 m_animation->setDuration(m_units->longDuration()); 0070 connect(m_units, &Kirigami::Platform::Units::longDurationChanged, m_animation, [this]() { 0071 m_animation->setDuration(m_units->longDuration()); 0072 }); 0073 updatePaintedGeometry(); 0074 } 0075 0076 void Icon::setSource(const QVariant &icon) 0077 { 0078 if (m_source == icon) { 0079 return; 0080 } 0081 m_source = icon; 0082 0083 if (!m_theme) { 0084 m_theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true)); 0085 Q_ASSERT(m_theme); 0086 0087 connect(m_theme, &Kirigami::Platform::PlatformTheme::colorsChanged, this, &QQuickItem::polish); 0088 } 0089 0090 if (m_networkReply) { 0091 // if there was a network query going on, interrupt it 0092 m_networkReply->close(); 0093 } 0094 m_loadedImage = QImage(); 0095 setStatus(Loading); 0096 0097 polish(); 0098 Q_EMIT sourceChanged(); 0099 Q_EMIT validChanged(); 0100 } 0101 0102 QVariant Icon::source() const 0103 { 0104 return m_source; 0105 } 0106 0107 void Icon::setActive(const bool active) 0108 { 0109 if (active == m_active) { 0110 return; 0111 } 0112 m_active = active; 0113 if (isComponentComplete()) { 0114 m_allowNextAnimation = true; 0115 } 0116 polish(); 0117 Q_EMIT activeChanged(); 0118 } 0119 0120 bool Icon::active() const 0121 { 0122 return m_active; 0123 } 0124 0125 bool Icon::valid() const 0126 { 0127 // TODO: should this be return m_status == Ready? 0128 // Consider an empty URL invalid, even though isNull() will say false 0129 if (m_source.canConvert<QUrl>() && m_source.toUrl().isEmpty()) { 0130 return false; 0131 } 0132 0133 return !m_source.isNull(); 0134 } 0135 0136 void Icon::setSelected(const bool selected) 0137 { 0138 if (selected == m_selected) { 0139 return; 0140 } 0141 m_selected = selected; 0142 polish(); 0143 Q_EMIT selectedChanged(); 0144 } 0145 0146 bool Icon::selected() const 0147 { 0148 return m_selected; 0149 } 0150 0151 void Icon::setIsMask(bool mask) 0152 { 0153 if (m_isMask == mask) { 0154 return; 0155 } 0156 0157 m_isMask = mask; 0158 polish(); 0159 Q_EMIT isMaskChanged(); 0160 } 0161 0162 bool Icon::isMask() const 0163 { 0164 return m_isMask; 0165 } 0166 0167 void Icon::setColor(const QColor &color) 0168 { 0169 if (m_color == color) { 0170 return; 0171 } 0172 0173 m_color = color; 0174 polish(); 0175 Q_EMIT colorChanged(); 0176 } 0177 0178 QColor Icon::color() const 0179 { 0180 return m_color; 0181 } 0182 0183 QSGNode *Icon::createSubtree(qreal initialOpacity) 0184 { 0185 auto opacityNode = new QSGOpacityNode{}; 0186 opacityNode->setFlag(QSGNode::OwnedByParent, true); 0187 opacityNode->setOpacity(initialOpacity); 0188 0189 auto *mNode = new ManagedTextureNode; 0190 0191 mNode->setTexture(s_iconImageCache->loadTexture(window(), m_icon, QQuickWindow::TextureCanUseAtlas)); 0192 0193 opacityNode->appendChildNode(mNode); 0194 0195 return opacityNode; 0196 } 0197 0198 void Icon::updateSubtree(QSGNode *node, qreal opacity) 0199 { 0200 auto opacityNode = static_cast<QSGOpacityNode *>(node); 0201 opacityNode->setOpacity(opacity); 0202 0203 auto textureNode = static_cast<ManagedTextureNode *>(opacityNode->firstChild()); 0204 textureNode->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest); 0205 } 0206 0207 QSGNode *Icon::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData * /*data*/) 0208 { 0209 if (m_source.isNull() || qFuzzyIsNull(width()) || qFuzzyIsNull(height())) { 0210 delete node; 0211 return Q_NULLPTR; 0212 } 0213 0214 if (!node) { 0215 node = new QSGNode{}; 0216 } 0217 0218 if (m_animation && m_animation->state() == QAbstractAnimation::Running) { 0219 if (node->childCount() < 2) { 0220 node->appendChildNode(createSubtree(0.0)); 0221 m_textureChanged = true; 0222 } 0223 0224 // Rather than doing a perfect crossfade, first fade in the new texture 0225 // then fade out the old texture. This is done to avoid the underlying 0226 // color bleeding through when both textures are at ~0.5 opacity, which 0227 // causes flickering if the two textures are very similar. 0228 updateSubtree(node->firstChild(), 2.0 - m_animValue * 2.0); 0229 updateSubtree(node->lastChild(), m_animValue * 2.0); 0230 } else { 0231 if (node->childCount() == 0) { 0232 node->appendChildNode(createSubtree(1.0)); 0233 m_textureChanged = true; 0234 } 0235 0236 if (node->childCount() > 1) { 0237 auto toRemove = node->firstChild(); 0238 node->removeChildNode(toRemove); 0239 delete toRemove; 0240 } 0241 0242 updateSubtree(node->firstChild(), 1.0); 0243 } 0244 0245 if (m_textureChanged) { 0246 auto mNode = static_cast<ManagedTextureNode *>(node->lastChild()->firstChild()); 0247 mNode->setTexture(s_iconImageCache->loadTexture(window(), m_icon, QQuickWindow::TextureCanUseAtlas)); 0248 m_textureChanged = false; 0249 m_sizeChanged = true; 0250 } 0251 0252 if (m_sizeChanged) { 0253 const QSizeF iconPixSize(m_icon.width() / m_devicePixelRatio, m_icon.height() / m_devicePixelRatio); 0254 const QSizeF itemPixSize = QSizeF((size() * m_devicePixelRatio).toSize()) / m_devicePixelRatio; 0255 QRectF nodeRect(QPoint(0, 0), itemPixSize); 0256 0257 if (itemPixSize.width() != 0 && itemPixSize.height() != 0) { 0258 if (iconPixSize != itemPixSize) { 0259 // At this point, the image will already be scaled, but we need to output it in 0260 // the correct aspect ratio, painted centered in the viewport. So: 0261 QRectF destination(QPointF(0, 0), QSizeF(m_icon.size()).scaled(m_paintedSize, Qt::KeepAspectRatio)); 0262 destination.moveCenter(nodeRect.center()); 0263 destination.moveTopLeft(QPointF(destination.topLeft().toPoint() * m_devicePixelRatio) / m_devicePixelRatio); 0264 nodeRect = destination; 0265 } 0266 } 0267 0268 // Adjust the final node on the pixel grid 0269 QPointF globalPixelPos = mapToScene(nodeRect.topLeft()) * m_devicePixelRatio; 0270 QPointF posAdjust = QPointF(globalPixelPos.x() - std::round(globalPixelPos.x()), globalPixelPos.y() - std::round(globalPixelPos.y())); 0271 nodeRect.moveTopLeft(nodeRect.topLeft() - posAdjust); 0272 0273 for (int i = 0; i < node->childCount(); ++i) { 0274 auto mNode = static_cast<ManagedTextureNode *>(node->childAtIndex(i)->firstChild()); 0275 mNode->setRect(nodeRect); 0276 } 0277 0278 m_sizeChanged = false; 0279 } 0280 0281 return node; 0282 } 0283 0284 void Icon::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) 0285 { 0286 QQuickItem::geometryChange(newGeometry, oldGeometry); 0287 if (newGeometry.size() != oldGeometry.size()) { 0288 m_sizeChanged = true; 0289 updatePaintedGeometry(); 0290 polish(); 0291 } 0292 } 0293 0294 void Icon::handleRedirect(QNetworkReply *reply) 0295 { 0296 QNetworkAccessManager *qnam = reply->manager(); 0297 if (reply->error() != QNetworkReply::NoError) { 0298 return; 0299 } 0300 const QUrl possibleRedirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); 0301 if (!possibleRedirectUrl.isEmpty()) { 0302 const QUrl redirectUrl = reply->url().resolved(possibleRedirectUrl); 0303 if (redirectUrl == reply->url()) { 0304 // no infinite redirections thank you very much 0305 reply->deleteLater(); 0306 return; 0307 } 0308 reply->deleteLater(); 0309 QNetworkRequest request(possibleRedirectUrl); 0310 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); 0311 m_networkReply = qnam->get(request); 0312 connect(m_networkReply.data(), &QNetworkReply::finished, this, [this]() { 0313 handleFinished(m_networkReply); 0314 }); 0315 } 0316 } 0317 0318 void Icon::handleFinished(QNetworkReply *reply) 0319 { 0320 if (!reply) { 0321 return; 0322 } 0323 0324 reply->deleteLater(); 0325 if (!reply->attribute(QNetworkRequest::RedirectionTargetAttribute).isNull()) { 0326 handleRedirect(reply); 0327 return; 0328 } 0329 0330 m_loadedImage = QImage(); 0331 0332 const QString filename = reply->url().fileName(); 0333 if (!m_loadedImage.load(reply, filename.mid(filename.indexOf(QLatin1Char('.'))).toLatin1().constData())) { 0334 qCWarning(KirigamiLog) << "received broken image" << reply->url(); 0335 0336 // broken image from data, inform the user of this with some useful broken-image thing... 0337 m_loadedImage = iconPixmap(QIcon::fromTheme(m_fallback)); 0338 } 0339 0340 polish(); 0341 } 0342 0343 void Icon::updatePolish() 0344 { 0345 QQuickItem::updatePolish(); 0346 0347 if (window()) { 0348 m_devicePixelRatio = window()->effectiveDevicePixelRatio(); 0349 } 0350 0351 if (m_source.isNull()) { 0352 setStatus(Ready); 0353 updatePaintedGeometry(); 0354 update(); 0355 return; 0356 } 0357 0358 const QSize itemSize(width(), height()); 0359 if (itemSize.width() != 0 && itemSize.height() != 0) { 0360 const QSize size = itemSize; 0361 0362 if (m_animation) { 0363 m_animation->stop(); 0364 m_oldIcon = m_icon; 0365 } 0366 0367 switch (m_source.userType()) { 0368 case QMetaType::QPixmap: 0369 m_icon = m_source.value<QPixmap>().toImage(); 0370 break; 0371 case QMetaType::QImage: 0372 m_icon = m_source.value<QImage>(); 0373 break; 0374 case QMetaType::QBitmap: 0375 m_icon = m_source.value<QBitmap>().toImage(); 0376 break; 0377 case QMetaType::QIcon: { 0378 m_icon = iconPixmap(m_source.value<QIcon>()); 0379 break; 0380 } 0381 case QMetaType::QUrl: 0382 case QMetaType::QString: 0383 m_icon = findIcon(size); 0384 break; 0385 case QMetaType::QBrush: 0386 // todo: fill here too? 0387 case QMetaType::QColor: 0388 m_icon = QImage(size, QImage::Format_Alpha8); 0389 m_icon.fill(m_source.value<QColor>()); 0390 break; 0391 default: 0392 break; 0393 } 0394 0395 if (m_icon.isNull()) { 0396 m_icon = QImage(size, QImage::Format_Alpha8); 0397 m_icon.fill(Qt::transparent); 0398 } 0399 0400 const QColor tintColor = // 0401 !m_color.isValid() || m_color == Qt::transparent // 0402 ? (m_selected ? m_theme->highlightedTextColor() : m_theme->textColor()) 0403 : m_color; 0404 0405 // TODO: initialize m_isMask with icon.isMask() 0406 if (tintColor.alpha() > 0 && isMask()) { 0407 QPainter p(&m_icon); 0408 p.setCompositionMode(QPainter::CompositionMode_SourceIn); 0409 p.fillRect(m_icon.rect(), tintColor); 0410 p.end(); 0411 } 0412 } 0413 0414 // don't animate initial setting 0415 bool animated = (m_animated || m_allowNextAnimation) && !m_oldIcon.isNull() && !m_sizeChanged && !m_blockNextAnimation; 0416 0417 if (animated && m_animation) { 0418 m_animValue = 0.0; 0419 m_animation->setStartValue((qreal)0); 0420 m_animation->setEndValue((qreal)1); 0421 m_animation->start(); 0422 m_allowNextAnimation = false; 0423 } else { 0424 if (m_animation) { 0425 m_animation->stop(); 0426 } 0427 m_animValue = 1.0; 0428 m_blockNextAnimation = false; 0429 } 0430 m_textureChanged = true; 0431 updatePaintedGeometry(); 0432 update(); 0433 } 0434 0435 QImage Icon::findIcon(const QSize &size) 0436 { 0437 QImage img; 0438 QString iconSource = m_source.toString(); 0439 0440 if (iconSource.startsWith(QLatin1String("image://"))) { 0441 QUrl iconUrl(iconSource); 0442 QString iconProviderId = iconUrl.host(); 0443 // QUrl path has the "/" prefix while iconId does not 0444 QString iconId = iconUrl.path().remove(0, 1); 0445 0446 QSize actualSize; 0447 QQuickImageProvider *imageProvider = dynamic_cast<QQuickImageProvider *>(qmlEngine(this)->imageProvider(iconProviderId)); 0448 if (!imageProvider) { 0449 return img; 0450 } 0451 switch (imageProvider->imageType()) { 0452 case QQmlImageProviderBase::Image: 0453 img = imageProvider->requestImage(iconId, &actualSize, size); 0454 if (!img.isNull()) { 0455 setStatus(Ready); 0456 } 0457 break; 0458 case QQmlImageProviderBase::Pixmap: 0459 img = imageProvider->requestPixmap(iconId, &actualSize, size).toImage(); 0460 if (!img.isNull()) { 0461 setStatus(Ready); 0462 } 0463 break; 0464 case QQmlImageProviderBase::ImageResponse: { 0465 if (!m_loadedImage.isNull()) { 0466 setStatus(Ready); 0467 return m_loadedImage.scaled(size, Qt::KeepAspectRatio, smooth() ? Qt::SmoothTransformation : Qt::FastTransformation); 0468 } 0469 QQuickAsyncImageProvider *provider = dynamic_cast<QQuickAsyncImageProvider *>(imageProvider); 0470 auto response = provider->requestImageResponse(iconId, size); 0471 connect(response, &QQuickImageResponse::finished, this, [iconId, response, this]() { 0472 if (response->errorString().isEmpty()) { 0473 QQuickTextureFactory *textureFactory = response->textureFactory(); 0474 if (textureFactory) { 0475 m_loadedImage = textureFactory->image(); 0476 delete textureFactory; 0477 } 0478 if (m_loadedImage.isNull()) { 0479 // broken image from data, inform the user of this with some useful broken-image thing... 0480 m_loadedImage = iconPixmap(QIcon::fromTheme(m_fallback)); 0481 setStatus(Error); 0482 } else { 0483 setStatus(Ready); 0484 } 0485 polish(); 0486 } 0487 response->deleteLater(); 0488 }); 0489 // Temporary icon while we wait for the real image to load... 0490 img = iconPixmap(QIcon::fromTheme(m_placeholder)); 0491 break; 0492 } 0493 case QQmlImageProviderBase::Texture: { 0494 QQuickTextureFactory *textureFactory = imageProvider->requestTexture(iconId, &actualSize, size); 0495 if (textureFactory) { 0496 img = textureFactory->image(); 0497 } 0498 if (img.isNull()) { 0499 // broken image from data, or the texture factory wasn't healthy, inform the user of this with some useful broken-image thing... 0500 img = iconPixmap(QIcon::fromTheme(m_fallback)); 0501 setStatus(Error); 0502 } else { 0503 setStatus(Ready); 0504 } 0505 break; 0506 } 0507 case QQmlImageProviderBase::Invalid: 0508 // will have to investigate this more 0509 setStatus(Error); 0510 break; 0511 } 0512 } else if (iconSource.startsWith(QLatin1String("http://")) || iconSource.startsWith(QLatin1String("https://"))) { 0513 if (!m_loadedImage.isNull()) { 0514 setStatus(Ready); 0515 return m_loadedImage.scaled(size, Qt::KeepAspectRatio, smooth() ? Qt::SmoothTransformation : Qt::FastTransformation); 0516 } 0517 const auto url = m_source.toUrl(); 0518 QQmlEngine *engine = qmlEngine(this); 0519 QNetworkAccessManager *qnam; 0520 if (engine && (qnam = engine->networkAccessManager()) && (!m_networkReply || m_networkReply->url() != url)) { 0521 QNetworkRequest request(url); 0522 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); 0523 m_networkReply = qnam->get(request); 0524 connect(m_networkReply.data(), &QNetworkReply::finished, this, [this]() { 0525 handleFinished(m_networkReply); 0526 }); 0527 } 0528 // Temporary icon while we wait for the real image to load... 0529 img = iconPixmap(QIcon::fromTheme(m_placeholder)); 0530 } else { 0531 if (iconSource.startsWith(QLatin1String("qrc:/"))) { 0532 iconSource = iconSource.mid(3); 0533 } else if (iconSource.startsWith(QLatin1String("file:/"))) { 0534 iconSource = QUrl(iconSource).path(); 0535 } 0536 0537 QIcon icon; 0538 const bool isPath = iconSource.contains(QLatin1String("/")); 0539 if (isPath) { 0540 icon = QIcon(iconSource); 0541 } else { 0542 const QColor tintColor = 0543 !m_color.isValid() || m_color == Qt::transparent ? (m_selected ? m_theme->highlightedTextColor() : m_theme->textColor()) : m_color; 0544 icon = m_theme->iconFromTheme(iconSource, tintColor); 0545 } 0546 if (!icon.isNull()) { 0547 img = iconPixmap(icon); 0548 setStatus(Ready); 0549 } 0550 } 0551 0552 if (!iconSource.isEmpty() && img.isNull()) { 0553 setStatus(Error); 0554 img = iconPixmap(QIcon::fromTheme(m_fallback)); 0555 } 0556 return img; 0557 } 0558 0559 QIcon::Mode Icon::iconMode() const 0560 { 0561 if (!isEnabled()) { 0562 return QIcon::Disabled; 0563 } else if (m_selected) { 0564 return QIcon::Selected; 0565 } else if (m_active) { 0566 return QIcon::Active; 0567 } 0568 return QIcon::Normal; 0569 } 0570 0571 QString Icon::fallback() const 0572 { 0573 return m_fallback; 0574 } 0575 0576 void Icon::setFallback(const QString &fallback) 0577 { 0578 if (m_fallback != fallback) { 0579 m_fallback = fallback; 0580 Q_EMIT fallbackChanged(fallback); 0581 } 0582 } 0583 0584 QString Icon::placeholder() const 0585 { 0586 return m_placeholder; 0587 } 0588 0589 void Icon::setPlaceholder(const QString &placeholder) 0590 { 0591 if (m_placeholder != placeholder) { 0592 m_placeholder = placeholder; 0593 Q_EMIT placeholderChanged(placeholder); 0594 } 0595 } 0596 0597 void Icon::setStatus(Status status) 0598 { 0599 if (status == m_status) { 0600 return; 0601 } 0602 0603 m_status = status; 0604 Q_EMIT statusChanged(); 0605 } 0606 0607 Icon::Status Icon::status() const 0608 { 0609 return m_status; 0610 } 0611 0612 qreal Icon::paintedWidth() const 0613 { 0614 return std::round(m_paintedSize.width()); 0615 } 0616 0617 qreal Icon::paintedHeight() const 0618 { 0619 return std::round(m_paintedSize.height()); 0620 } 0621 0622 QSize Icon::iconSizeHint() const 0623 { 0624 if (!m_roundToIconSize) { 0625 return QSize(width(), height()); 0626 } else if (m_units) { 0627 return QSize(m_units->iconSizes()->roundedIconSize(std::min(width(), height())), m_units->iconSizes()->roundedIconSize(std::min(width(), height()))); 0628 } else { 0629 return QSize(std::min(width(), height()), std::min(width(), height())); 0630 } 0631 } 0632 0633 QImage Icon::iconPixmap(const QIcon &icon) const 0634 { 0635 const QSize actualSize = icon.actualSize(iconSizeHint()); 0636 return icon.pixmap(actualSize, m_devicePixelRatio, iconMode(), QIcon::On).toImage(); 0637 } 0638 0639 void Icon::updatePaintedGeometry() 0640 { 0641 QSizeF newSize; 0642 if (!m_icon.width() || !m_icon.height()) { 0643 newSize = {0, 0}; 0644 } else { 0645 qreal roundedWidth = m_units ? m_units->iconSizes()->roundedIconSize(std::min(width(), height())) : 32; 0646 roundedWidth = std::round(roundedWidth * m_devicePixelRatio) / m_devicePixelRatio; 0647 0648 if (QSizeF roundedSize(roundedWidth, roundedWidth); size() == roundedSize) { 0649 m_paintedSize = roundedSize; 0650 m_textureChanged = true; 0651 update(); 0652 Q_EMIT paintedAreaChanged(); 0653 return; 0654 } 0655 if (m_roundToIconSize && m_units) { 0656 if (m_icon.width() > m_icon.height()) { 0657 newSize = QSizeF(roundedWidth, m_icon.height() * (roundedWidth / static_cast<qreal>(m_icon.width()))); 0658 } else { 0659 newSize = QSizeF(roundedWidth, roundedWidth); 0660 } 0661 } else { 0662 const QSizeF iconPixSize(m_icon.width() / m_devicePixelRatio, m_icon.height() / m_devicePixelRatio); 0663 0664 const qreal w = widthValid() ? width() : iconPixSize.width(); 0665 const qreal widthScale = w / iconPixSize.width(); 0666 const qreal h = heightValid() ? height() : iconPixSize.height(); 0667 const qreal heightScale = h / iconPixSize.height(); 0668 0669 if (widthScale <= heightScale) { 0670 newSize = QSizeF(w, widthScale * iconPixSize.height()); 0671 } else if (heightScale < widthScale) { 0672 newSize = QSizeF(heightScale * iconPixSize.width(), h); 0673 } 0674 } 0675 } 0676 if (newSize != m_paintedSize) { 0677 m_paintedSize = newSize; 0678 m_textureChanged = true; 0679 update(); 0680 Q_EMIT paintedAreaChanged(); 0681 } 0682 } 0683 0684 bool Icon::isAnimated() const 0685 { 0686 return m_animated; 0687 } 0688 0689 void Icon::setAnimated(bool animated) 0690 { 0691 if (m_animated == animated) { 0692 return; 0693 } 0694 0695 m_animated = animated; 0696 Q_EMIT animatedChanged(); 0697 } 0698 0699 bool Icon::roundToIconSize() const 0700 { 0701 return m_roundToIconSize; 0702 } 0703 0704 void Icon::setRoundToIconSize(bool roundToIconSize) 0705 { 0706 if (m_roundToIconSize == roundToIconSize) { 0707 return; 0708 } 0709 0710 const QSizeF oldPaintedSize = m_paintedSize; 0711 0712 m_roundToIconSize = roundToIconSize; 0713 Q_EMIT roundToIconSizeChanged(); 0714 0715 updatePaintedGeometry(); 0716 if (oldPaintedSize != m_paintedSize) { 0717 Q_EMIT paintedAreaChanged(); 0718 m_textureChanged = true; 0719 update(); 0720 } 0721 } 0722 0723 void Icon::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) 0724 { 0725 if (change == QQuickItem::ItemDevicePixelRatioHasChanged) { 0726 m_blockNextAnimation = true; 0727 if (window()) { 0728 m_devicePixelRatio = window()->effectiveDevicePixelRatio(); 0729 } 0730 polish(); 0731 } else if (change == QQuickItem::ItemSceneChange) { 0732 if (m_window) { 0733 disconnect(m_window.data(), &QWindow::visibleChanged, this, &Icon::windowVisibleChanged); 0734 } 0735 m_window = value.window; 0736 if (m_window) { 0737 connect(m_window.data(), &QWindow::visibleChanged, this, &Icon::windowVisibleChanged); 0738 m_devicePixelRatio = m_window->effectiveDevicePixelRatio(); 0739 } 0740 } else if (change == ItemVisibleHasChanged && value.boolValue) { 0741 m_blockNextAnimation = true; 0742 } 0743 QQuickItem::itemChange(change, value); 0744 } 0745 0746 void Icon::valueChanged(const QVariant &value) 0747 { 0748 m_animValue = value.toReal(); 0749 update(); 0750 } 0751 0752 void Icon::windowVisibleChanged(bool visible) 0753 { 0754 if (visible) { 0755 m_blockNextAnimation = true; 0756 } 0757 } 0758 0759 #include "moc_icon.cpp"