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 {
source: "image://materialBrowserTex/" + textureSource
asynchronous: true
sourceSize.width: root.width - 10
sourceSize.height: root.height - 10
anchors.centerIn: parent
cache: false
smooth: true
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,7 @@
#include <designeractionmanager.h>
#include <designermcumanager.h>
#include <documentmanager.h>
#include <propertyeditorimageprovider.h>
#include <qmldesignerconstants.h>
#include <qmldesignerplugin.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)
{
if (event->type() == QEvent::FocusOut) {
@@ -145,9 +119,16 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event)
QString iconPath = QLatin1String("%1/%2")
.arg(DocumentManager::currentResourcePath().path(),
m_textureToDrag.variantProperty("source").value().toString());
model->startDrag(mimeData,
m_textureImageProvider->requestPixmap(iconPath, nullptr,
{128, 128}));
QPixmap pixmap;
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_textureToDrag = {};
@@ -161,14 +142,18 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event)
return QObject::eventFilter(obj, event);
}
MaterialBrowserWidget::MaterialBrowserWidget(MaterialBrowserView *view)
MaterialBrowserWidget::MaterialBrowserWidget(AsynchronousImageCache &imageCache,
MaterialBrowserView *view)
: m_materialBrowserView(view)
, m_materialBrowserModel(new MaterialBrowserModel(this))
, m_materialBrowserTexturesModel(new MaterialBrowserTexturesModel(this))
, m_quickWidget(new QQuickWidget(this))
, 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"));
setMinimumWidth(120);

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,7 @@
#include "bindingproperty.h"
#include "documentmanager.h"
#include "nodemetainfo.h"
#include "propertyeditorimageprovider.h"
#include "propertyeditorvalue.h"
#include "qmldesignerconstants.h"
#include "qmlobjectnode.h"
@@ -41,46 +42,17 @@ static QObject *variantToQObject(const QVariant &value)
namespace QmlDesigner {
class TextureEditorImageProvider : public QQuickImageProvider
{
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)
TextureEditorQmlBackend::TextureEditorQmlBackend(TextureEditorView *textureEditor, AsynchronousImageCache &imageCache)
: m_view(new QQuickWidget)
, m_textureEditorTransaction(new TextureEditorTransaction(textureEditor))
, 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->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->setModel(textureEditor->model());
context()->setContextObject(m_contextObject.data());

View File

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

View File

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

View File

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

View File

@@ -14,6 +14,7 @@
#include <projectexplorer/target.h>
#include <utils/fileutils.h>
#include <QGuiApplication>
#include <QPlainTextEdit>
namespace QmlDesigner {
@@ -103,7 +104,11 @@ void ImageCacheCollector::start(Utils::SmallStringView name,
auto callback = [=, captureCallback = std::move(captureCallback)](const QImage &image) {
if (nullImageHandling == ImageCacheCollectorNullImageHandling::CaptureNullImage
|| !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);
QImage smallImage = image.isNull() ? QImage{}
: image.scaled(smallImageSize,

View File

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