QmlDesigner: Unify texture image providers

Texture editor, material browser, and UrlChooser all had separate
texture providers that served nearly identical purpose.
Unified all use cases to use same PropertyEditorImageProvider.
This provider is asynchronous, which combined with enabling caching on
Image elements, significantly improves responsiveness of the material
browser UI when there are many textures shown in the browser.

Fixes: QDS-8387
Change-Id: I2888aee2f4320dba9456fa046c9ede319673a3d9
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
Miikka Heikkinen
2022-11-24 17:30:12 +02:00
parent 3a899b34c7
commit ddecd338a8
17 changed files with 95 additions and 101 deletions

View File

@@ -40,10 +40,10 @@ Rectangle {
Image { Image {
source: "image://materialBrowserTex/" + textureSource source: "image://materialBrowserTex/" + textureSource
asynchronous: true
sourceSize.width: root.width - 10 sourceSize.width: root.width - 10
sourceSize.height: root.height - 10 sourceSize.height: root.height - 10
anchors.centerIn: parent anchors.centerIn: parent
cache: false
smooth: true smooth: true
} }
} }

View File

@@ -102,8 +102,8 @@ Row {
Item { Item {
visible: thumbnail.status === Image.Ready visible: thumbnail.status === Image.Ready
Layout.preferredWidth: 100 Layout.preferredWidth: 96
Layout.preferredHeight: 100 Layout.preferredHeight: 96
Image { Image {
id: checker id: checker
@@ -116,7 +116,10 @@ Row {
Image { Image {
id: thumbnail id: thumbnail
asynchronous: true asynchronous: true
anchors.fill: parent sourceSize.height: 96
sourceSize.width: 96
height: 96
width: 96
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
source: { source: {
if (root.isBuiltInPrimitive(root.absoluteFilePath)) if (root.isBuiltInPrimitive(root.absoluteFilePath))
@@ -231,8 +234,8 @@ Row {
Item { Item {
visible: delegateThumbnail.status === Image.Ready visible: delegateThumbnail.status === Image.Ready
Layout.preferredWidth: 100 Layout.preferredWidth: 96
Layout.preferredHeight: 100 Layout.preferredHeight: 96
Image { Image {
id: delegateChecker id: delegateChecker
@@ -245,7 +248,10 @@ Row {
Image { Image {
id: delegateThumbnail id: delegateThumbnail
asynchronous: true asynchronous: true
anchors.fill: parent sourceSize.height: 96
sourceSize.width: 96
height: 96
width: 96
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
source: { source: {
if (root.isBuiltInPrimitive(delegateRoot.name)) if (root.isBuiltInPrimitive(delegateRoot.name))

View File

@@ -11,7 +11,7 @@ Column {
function refreshPreview() function refreshPreview()
{ {
texturePreview.source = "" texturePreview.source = ""
texturePreview.source = "image://textureEditor/" + backendValues.source.valueToString texturePreview.source = "image://qmldesigner_thumbnails/" + resolveResourcePath(backendValues.source.valueToString)
} }
anchors.left: parent.left anchors.left: parent.left
@@ -34,12 +34,11 @@ Column {
Image { Image {
id: texturePreview id: texturePreview
asynchronous: true
sourceSize.width: 150 sourceSize.width: 150
sourceSize.height: 150 sourceSize.height: 150
anchors.centerIn: parent anchors.centerIn: parent
source: "image://textureEditor/" + backendValues.source.valueToString source: "image://qmldesigner_thumbnails/" + resolveResourcePath(backendValues.source.valueToString)
cache: false
} }
} }
} }

View File

@@ -69,8 +69,8 @@ public:
, navigatorView{externalDependencies} , navigatorView{externalDependencies}
, propertyEditorView(imageCache, externalDependencies) , propertyEditorView(imageCache, externalDependencies)
, materialEditorView{externalDependencies} , materialEditorView{externalDependencies}
, materialBrowserView{externalDependencies} , materialBrowserView{imageCache, externalDependencies}
, textureEditorView{externalDependencies} , textureEditorView{imageCache, externalDependencies}
, statesEditorView{externalDependencies} , statesEditorView{externalDependencies}
, newStatesEditorView{externalDependencies} , newStatesEditorView{externalDependencies}
{} {}

View File

@@ -41,8 +41,10 @@ static QString propertyEditorResourcesPath()
return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString(); return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString();
} }
MaterialBrowserView::MaterialBrowserView(ExternalDependenciesInterface &externalDependencies) MaterialBrowserView::MaterialBrowserView(AsynchronousImageCache &imageCache,
ExternalDependenciesInterface &externalDependencies)
: AbstractView{externalDependencies} : AbstractView{externalDependencies}
, m_imageCache(imageCache)
{ {
m_previewTimer.setSingleShot(true); m_previewTimer.setSingleShot(true);
connect(&m_previewTimer, &QTimer::timeout, this, &MaterialBrowserView::requestPreviews); connect(&m_previewTimer, &QTimer::timeout, this, &MaterialBrowserView::requestPreviews);
@@ -59,12 +61,11 @@ bool MaterialBrowserView::hasWidget() const
WidgetInfo MaterialBrowserView::widgetInfo() WidgetInfo MaterialBrowserView::widgetInfo()
{ {
if (m_widget.isNull()) { if (m_widget.isNull()) {
m_widget = new MaterialBrowserWidget(this); m_widget = new MaterialBrowserWidget(m_imageCache, this);
auto matEditorContext = new Internal::MaterialBrowserContext(m_widget.data()); auto matEditorContext = new Internal::MaterialBrowserContext(m_widget.data());
Core::ICore::addContextObject(matEditorContext); Core::ICore::addContextObject(matEditorContext);
// custom notifications below are sent to the MaterialEditor // custom notifications below are sent to the MaterialEditor
MaterialBrowserModel *matBrowserModel = m_widget->materialBrowserModel().data(); MaterialBrowserModel *matBrowserModel = m_widget->materialBrowserModel().data();

View File

@@ -22,7 +22,8 @@ class MaterialBrowserView : public AbstractView
Q_OBJECT Q_OBJECT
public: public:
MaterialBrowserView(ExternalDependenciesInterface &externalDependencies); MaterialBrowserView(class AsynchronousImageCache &imageCache,
ExternalDependenciesInterface &externalDependencies);
~MaterialBrowserView() override; ~MaterialBrowserView() override;
bool hasWidget() const override; bool hasWidget() const override;
@@ -64,6 +65,7 @@ private:
void loadPropertyGroups(); void loadPropertyGroups();
void requestPreviews(); void requestPreviews();
AsynchronousImageCache &m_imageCache;
QPointer<MaterialBrowserWidget> m_widget; QPointer<MaterialBrowserWidget> m_widget;
QList<ModelNode> m_selectedModels; // selected 3D model nodes QList<ModelNode> m_selectedModels; // selected 3D model nodes

View File

@@ -10,6 +10,7 @@
#include <designeractionmanager.h> #include <designeractionmanager.h>
#include <designermcumanager.h> #include <designermcumanager.h>
#include <documentmanager.h> #include <documentmanager.h>
#include <propertyeditorimageprovider.h>
#include <qmldesignerconstants.h> #include <qmldesignerconstants.h>
#include <qmldesignerplugin.h> #include <qmldesignerplugin.h>
#include <variantproperty.h> #include <variantproperty.h>
@@ -89,33 +90,6 @@ public:
} }
}; };
class TextureImageProvider : public QQuickImageProvider
{
public:
TextureImageProvider() : QQuickImageProvider(Pixmap) {}
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override
{
QPixmap pixmap;
const QString suffix = id.split('.').last().toLower();
if (suffix == "hdr")
pixmap = HdrImage{id}.toPixmap();
else
pixmap = Utils::StyleHelper::dpiSpecificImageFile(id);
if (pixmap.isNull())
pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png");
if (size)
*size = pixmap.size();
if (requestedSize.isValid())
return pixmap.scaled(requestedSize, Qt::KeepAspectRatio);
return pixmap;
}
};
bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event)
{ {
if (event->type() == QEvent::FocusOut) { if (event->type() == QEvent::FocusOut) {
@@ -145,9 +119,16 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event)
QString iconPath = QLatin1String("%1/%2") QString iconPath = QLatin1String("%1/%2")
.arg(DocumentManager::currentResourcePath().path(), .arg(DocumentManager::currentResourcePath().path(),
m_textureToDrag.variantProperty("source").value().toString()); m_textureToDrag.variantProperty("source").value().toString());
model->startDrag(mimeData,
m_textureImageProvider->requestPixmap(iconPath, nullptr, QPixmap pixmap;
{128, 128})); const QString suffix = iconPath.split('.').last().toLower();
if (suffix == "hdr")
pixmap = HdrImage{iconPath}.toPixmap();
else
pixmap = Utils::StyleHelper::dpiSpecificImageFile(iconPath);
if (pixmap.isNull())
pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png");
model->startDrag(mimeData, pixmap.scaled({128, 128}));
} }
m_materialToDrag = {}; m_materialToDrag = {};
m_textureToDrag = {}; m_textureToDrag = {};
@@ -161,14 +142,18 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event)
return QObject::eventFilter(obj, event); return QObject::eventFilter(obj, event);
} }
MaterialBrowserWidget::MaterialBrowserWidget(MaterialBrowserView *view) MaterialBrowserWidget::MaterialBrowserWidget(AsynchronousImageCache &imageCache,
MaterialBrowserView *view)
: m_materialBrowserView(view) : m_materialBrowserView(view)
, m_materialBrowserModel(new MaterialBrowserModel(this)) , m_materialBrowserModel(new MaterialBrowserModel(this))
, m_materialBrowserTexturesModel(new MaterialBrowserTexturesModel(this)) , m_materialBrowserTexturesModel(new MaterialBrowserTexturesModel(this))
, m_quickWidget(new QQuickWidget(this)) , m_quickWidget(new QQuickWidget(this))
, m_previewImageProvider(new PreviewImageProvider()) , m_previewImageProvider(new PreviewImageProvider())
, m_textureImageProvider(new TextureImageProvider())
{ {
QImage defaultImage;
defaultImage.load(Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png"));
m_textureImageProvider = new PropertyEditorImageProvider(imageCache, defaultImage);
setWindowTitle(tr("Material Browser", "Title of material browser widget")); setWindowTitle(tr("Material Browser", "Title of material browser widget"));
setMinimumWidth(120); setMinimumWidth(120);

View File

@@ -28,14 +28,14 @@ class MaterialBrowserView;
class MaterialBrowserModel; class MaterialBrowserModel;
class MaterialBrowserTexturesModel; class MaterialBrowserTexturesModel;
class PreviewImageProvider; class PreviewImageProvider;
class TextureImageProvider; class PropertyEditorImageProvider;
class MaterialBrowserWidget : public QFrame class MaterialBrowserWidget : public QFrame
{ {
Q_OBJECT Q_OBJECT
public: public:
MaterialBrowserWidget(MaterialBrowserView *view); MaterialBrowserWidget(class AsynchronousImageCache &imageCache, MaterialBrowserView *view);
~MaterialBrowserWidget() = default; ~MaterialBrowserWidget() = default;
QList<QToolButton *> createToolBarWidgets(); QList<QToolButton *> createToolBarWidgets();
@@ -72,7 +72,7 @@ private:
QShortcut *m_qmlSourceUpdateShortcut = nullptr; QShortcut *m_qmlSourceUpdateShortcut = nullptr;
PreviewImageProvider *m_previewImageProvider = nullptr; PreviewImageProvider *m_previewImageProvider = nullptr;
TextureImageProvider *m_textureImageProvider = nullptr; PropertyEditorImageProvider *m_textureImageProvider = nullptr;
Core::IContext *m_context = nullptr; Core::IContext *m_context = nullptr;
QString m_filterText; QString m_filterText;

View File

@@ -25,18 +25,25 @@ QQuickImageResponse *PropertyEditorImageProvider::requestImageResponse(const QSt
return m_smallImageCacheProvider.requestImageResponse("#" + id.split('.').first(), return m_smallImageCacheProvider.requestImageResponse("#" + id.split('.').first(),
requestedSize); requestedSize);
QImage image; auto response = std::make_unique<QmlDesigner::ImageResponse>(m_smallImageCacheProvider.defaultImage());
auto response = std::make_unique<QmlDesigner::ImageResponse>(image);
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
response.get(), response.get(),
[response = QPointer<QmlDesigner::ImageResponse>(response.get()), image, suffix, id] { [response = QPointer<QmlDesigner::ImageResponse>(response.get()), suffix, id, requestedSize] {
if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix)) if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix)) {
response->setImage(QImage(Utils::StyleHelper::dpiSpecificImageFile(id))); QImage image = QImage(Utils::StyleHelper::dpiSpecificImageFile(id));
else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix)) if (!image.isNull()) {
response->setImage(HdrImage{id}.image()); response->setImage(image.scaled(requestedSize, Qt::KeepAspectRatio));
else return;
response->abort(); }
} else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix)) {
HdrImage hdr{id};
if (!hdr.image().isNull()) {
response->setImage(hdr.image().scaled(requestedSize, Qt::KeepAspectRatio));
return;
}
}
response->setImage(response->image().scaled(requestedSize, Qt::KeepAspectRatio));
}, },
Qt::QueuedConnection); Qt::QueuedConnection);

View File

@@ -12,6 +12,7 @@
#include <coreplugin/messagebox.h> #include <coreplugin/messagebox.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/filepath.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <QApplication> #include <QApplication>
@@ -334,4 +335,11 @@ void TextureEditorContextObject::goIntoComponent()
DocumentManager::goIntoComponent(m_selectedTexture); DocumentManager::goIntoComponent(m_selectedTexture);
} }
QString TextureEditorContextObject::resolveResourcePath(const QString &path)
{
if (Utils::FilePath::fromString(path).isAbsolutePath())
return path;
return DocumentManager::currentResourcePath().path() + '/' + path;
}
} // QmlDesigner } // QmlDesigner

View File

@@ -73,6 +73,7 @@ public:
Q_INVOKABLE bool isBlocked(const QString &propName) const; Q_INVOKABLE bool isBlocked(const QString &propName) const;
Q_INVOKABLE void goIntoComponent(); Q_INVOKABLE void goIntoComponent();
Q_INVOKABLE QString resolveResourcePath(const QString &path);
enum ToolBarAction { enum ToolBarAction {
ApplyToSelected, ApplyToSelected,

View File

@@ -6,6 +6,7 @@
#include "bindingproperty.h" #include "bindingproperty.h"
#include "documentmanager.h" #include "documentmanager.h"
#include "nodemetainfo.h" #include "nodemetainfo.h"
#include "propertyeditorimageprovider.h"
#include "propertyeditorvalue.h" #include "propertyeditorvalue.h"
#include "qmldesignerconstants.h" #include "qmldesignerconstants.h"
#include "qmlobjectnode.h" #include "qmlobjectnode.h"
@@ -41,46 +42,17 @@ static QObject *variantToQObject(const QVariant &value)
namespace QmlDesigner { namespace QmlDesigner {
class TextureEditorImageProvider : public QQuickImageProvider TextureEditorQmlBackend::TextureEditorQmlBackend(TextureEditorView *textureEditor, AsynchronousImageCache &imageCache)
{
QPixmap m_previewPixmap;
public:
TextureEditorImageProvider()
: QQuickImageProvider(Pixmap) {}
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override
{
QPixmap pixmap;
const QString suffix = id.split('.').last().toLower();
const QString path = DocumentManager::currentResourcePath().path() + '/' + id;
if (suffix == "hdr")
pixmap = HdrImage{path}.toPixmap();
else
pixmap = Utils::StyleHelper::dpiSpecificImageFile(path);
if (pixmap.isNull())
pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png");
if (size)
*size = pixmap.size();
if (requestedSize.isValid())
return pixmap.scaled(requestedSize, Qt::KeepAspectRatio);
return pixmap;
}
};
TextureEditorQmlBackend::TextureEditorQmlBackend(TextureEditorView *textureEditor)
: m_view(new QQuickWidget) : m_view(new QQuickWidget)
, m_textureEditorTransaction(new TextureEditorTransaction(textureEditor)) , m_textureEditorTransaction(new TextureEditorTransaction(textureEditor))
, m_contextObject(new TextureEditorContextObject(m_view->rootContext())) , m_contextObject(new TextureEditorContextObject(m_view->rootContext()))
, m_textureEditorImageProvider(new TextureEditorImageProvider())
{ {
QImage defaultImage;
defaultImage.load(Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png"));
m_textureEditorImageProvider = new PropertyEditorImageProvider(imageCache, defaultImage);
m_view->setResizeMode(QQuickWidget::SizeRootObjectToView); m_view->setResizeMode(QQuickWidget::SizeRootObjectToView);
m_view->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); m_view->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
m_view->engine()->addImageProvider("textureEditor", m_textureEditorImageProvider); m_view->engine()->addImageProvider("qmldesigner_thumbnails", m_textureEditorImageProvider);
m_contextObject->setBackendValues(&m_backendValuesPropertyMap); m_contextObject->setBackendValues(&m_backendValuesPropertyMap);
m_contextObject->setModel(textureEditor->model()); m_contextObject->setModel(textureEditor->model());
context()->setContextObject(m_contextObject.data()); context()->setContextObject(m_contextObject.data());

View File

@@ -17,6 +17,7 @@ QT_END_NAMESPACE
namespace QmlDesigner { namespace QmlDesigner {
class PropertyEditorImageProvider;
class TextureEditorContextObject; class TextureEditorContextObject;
class TextureEditorImageProvider; class TextureEditorImageProvider;
class TextureEditorTransaction; class TextureEditorTransaction;
@@ -27,7 +28,8 @@ class TextureEditorQmlBackend
Q_DISABLE_COPY(TextureEditorQmlBackend) Q_DISABLE_COPY(TextureEditorQmlBackend)
public: public:
TextureEditorQmlBackend(TextureEditorView *materialEditor); TextureEditorQmlBackend(TextureEditorView *materialEditor,
class AsynchronousImageCache &imageCache);
~TextureEditorQmlBackend(); ~TextureEditorQmlBackend();
void setup(const QmlObjectNode &selectedTextureNode, const QString &stateName, const QUrl &qmlSpecificsFile, void setup(const QmlObjectNode &selectedTextureNode, const QString &stateName, const QUrl &qmlSpecificsFile,
@@ -63,7 +65,7 @@ private:
DesignerPropertyMap m_backendValuesPropertyMap; DesignerPropertyMap m_backendValuesPropertyMap;
QScopedPointer<TextureEditorTransaction> m_textureEditorTransaction; QScopedPointer<TextureEditorTransaction> m_textureEditorTransaction;
QScopedPointer<TextureEditorContextObject> m_contextObject; QScopedPointer<TextureEditorContextObject> m_contextObject;
TextureEditorImageProvider *m_textureEditorImageProvider = nullptr; PropertyEditorImageProvider *m_textureEditorImageProvider = nullptr;
}; };
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -49,8 +49,10 @@
namespace QmlDesigner { namespace QmlDesigner {
TextureEditorView::TextureEditorView(ExternalDependenciesInterface &externalDependencies) TextureEditorView::TextureEditorView(AsynchronousImageCache &imageCache,
ExternalDependenciesInterface &externalDependencies)
: AbstractView{externalDependencies} : AbstractView{externalDependencies}
, m_imageCache(imageCache)
, m_stackedWidget(new QStackedWidget) , m_stackedWidget(new QStackedWidget)
, m_dynamicPropertiesModel(new Internal::DynamicPropertiesModel(true, this)) , m_dynamicPropertiesModel(new Internal::DynamicPropertiesModel(true, this))
{ {
@@ -432,7 +434,7 @@ void TextureEditorView::setupQmlBackend()
QString currentStateName = currentState().isBaseState() ? currentState().name() : "invalid state"; QString currentStateName = currentState().isBaseState() ? currentState().name() : "invalid state";
if (!currentQmlBackend) { if (!currentQmlBackend) {
currentQmlBackend = new TextureEditorQmlBackend(this); currentQmlBackend = new TextureEditorQmlBackend(this, m_imageCache);
m_stackedWidget->addWidget(currentQmlBackend->widget()); m_stackedWidget->addWidget(currentQmlBackend->widget());
m_qmlBackendHash.insert(qmlPaneUrl.toString(), currentQmlBackend); m_qmlBackendHash.insert(qmlPaneUrl.toString(), currentQmlBackend);

View File

@@ -31,7 +31,8 @@ class TextureEditorView : public AbstractView
Q_OBJECT Q_OBJECT
public: public:
TextureEditorView(ExternalDependenciesInterface &externalDependencies); TextureEditorView(class AsynchronousImageCache &imageCache,
ExternalDependenciesInterface &externalDependencies);
~TextureEditorView() override; ~TextureEditorView() override;
bool hasWidget() const override; bool hasWidget() const override;
@@ -105,6 +106,7 @@ private:
bool noValidSelection() const; bool noValidSelection() const;
AsynchronousImageCache &m_imageCache;
ModelNode m_selectedTexture; ModelNode m_selectedTexture;
QTimer m_ensureMatLibTimer; QTimer m_ensureMatLibTimer;
QShortcut *m_updateShortcut = nullptr; QShortcut *m_updateShortcut = nullptr;

View File

@@ -14,6 +14,7 @@
#include <projectexplorer/target.h> #include <projectexplorer/target.h>
#include <utils/fileutils.h> #include <utils/fileutils.h>
#include <QGuiApplication>
#include <QPlainTextEdit> #include <QPlainTextEdit>
namespace QmlDesigner { namespace QmlDesigner {
@@ -103,7 +104,11 @@ void ImageCacheCollector::start(Utils::SmallStringView name,
auto callback = [=, captureCallback = std::move(captureCallback)](const QImage &image) { auto callback = [=, captureCallback = std::move(captureCallback)](const QImage &image) {
if (nullImageHandling == ImageCacheCollectorNullImageHandling::CaptureNullImage if (nullImageHandling == ImageCacheCollectorNullImageHandling::CaptureNullImage
|| !image.isNull()) { || !image.isNull()) {
QSize smallImageSize = image.size().scaled(QSize{96, 96}.boundedTo(image.size()), QSize targetSize {96, 96};
const qreal ratio = qGuiApp->devicePixelRatio();
if (ratio > 1.0)
targetSize *= qRound(ratio);
QSize smallImageSize = image.size().scaled(targetSize.boundedTo(image.size()),
Qt::KeepAspectRatio); Qt::KeepAspectRatio);
QImage smallImage = image.isNull() ? QImage{} QImage smallImage = image.isNull() ? QImage{}
: image.scaled(smallImageSize, : image.scaled(smallImageSize,

View File

@@ -20,6 +20,7 @@ public:
QQuickTextureFactory *textureFactory() const override; QQuickTextureFactory *textureFactory() const override;
void setImage(const QImage &image); void setImage(const QImage &image);
QImage image() const { return m_image; }
void abort(); void abort();
@@ -37,6 +38,7 @@ public:
QQuickImageResponse *requestImageResponse(const QString &id, QQuickImageResponse *requestImageResponse(const QString &id,
const QSize &requestedSize) override; const QSize &requestedSize) override;
QImage defaultImage() const { return m_defaultImage; }
private: private:
AsynchronousImageCache &m_cache; AsynchronousImageCache &m_cache;