forked from qt-creator/qt-creator
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:
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -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))
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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}
|
||||
{}
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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());
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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,
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user