QmlDesigner: Make the material editor preview resizable

* Also the ui for the material editor preview is changed.

Task-number: QDS-12928
Change-Id: I37cdb5f5f0b701fd0eb9b00f837a7e5738829ea3
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
Ali Kianian
2024-06-03 12:38:03 +03:00
parent a30df83205
commit 9d872edce0
11 changed files with 230 additions and 110 deletions

View File

@@ -8,7 +8,7 @@ import HelperWidgets as HelperWidgets
import StudioControls as StudioControls import StudioControls as StudioControls
import StudioTheme as StudioTheme import StudioTheme as StudioTheme
Column { ColumnLayout {
id: root id: root
property string previewEnv property string previewEnv
@@ -18,7 +18,7 @@ Column {
property StudioTheme.ControlStyle buttonStyle: StudioTheme.ViewBarButtonStyle { property StudioTheme.ControlStyle buttonStyle: StudioTheme.ViewBarButtonStyle {
//This is how you can override stuff from the control styles //This is how you can override stuff from the control styles
controlSize: Qt.size(previewOptions.width, previewOptions.width) controlSize: Qt.size(optionsToolbar.height, optionsToolbar.height)
baseIconFontSize: StudioTheme.Values.bigIconFontSize baseIconFontSize: StudioTheme.Values.bigIconFontSize
} }
@@ -118,16 +118,16 @@ Column {
} }
} }
Item { Row {
anchors.horizontalCenter: parent.horizontalCenter id: optionsToolbar
width: parent.width
height: previewRect.height
Layout.preferredHeight: 40
Layout.fillWidth: true
leftPadding: root.__horizontalSpacing
StudioControls.AbstractButton { StudioControls.AbstractButton {
id: pinButton id: pinButton
x: root.__horizontalSpacing
style: root.buttonStyle style: root.buttonStyle
iconSize: StudioTheme.Values.bigFont iconSize: StudioTheme.Values.bigFont
buttonIcon: pinButton.checked ? StudioTheme.Constants.pin : StudioTheme.Constants.unpin buttonIcon: pinButton.checked ? StudioTheme.Constants.pin : StudioTheme.Constants.unpin
@@ -136,56 +136,53 @@ Column {
onCheckedChanged: itemPane.headerDocked = pinButton.checked onCheckedChanged: itemPane.headerDocked = pinButton.checked
} }
Rectangle { HelperWidgets.AbstractButton {
id: previewRect style: root.buttonStyle
anchors.horizontalCenter: parent.horizontalCenter buttonIcon: StudioTheme.Constants.textures_medium
width: 152 tooltip: qsTr("Select preview environment.")
height: 152 onClicked: envMenu.popup()
color: "#000000"
Image {
id: materialPreview
width: 150
height: 150
anchors.centerIn: parent
source: "image://materialEditor/preview"
cache: false
smooth: true
}
} }
Item { HelperWidgets.AbstractButton {
id: previewOptions style: root.buttonStyle
width: 40 buttonIcon: StudioTheme.Constants.cube_medium
height: previewRect.height tooltip: qsTr("Select preview model.")
anchors.top: previewRect.top onClicked: modelMenu.popup()
anchors.left: previewRect.right }
anchors.leftMargin: root.__horizontalSpacing }
Column { Rectangle {
anchors.horizontalCenter: parent.horizontalCenter id: previewRect
HelperWidgets.AbstractButton { Layout.fillWidth: true
style: root.buttonStyle Layout.minimumWidth: 152
buttonIcon: StudioTheme.Constants.textures_medium implicitHeight: materialPreview.height
tooltip: qsTr("Select preview environment.")
onClicked: envMenu.popup()
}
HelperWidgets.AbstractButton { clip: true
style: root.buttonStyle color: "#000000"
buttonIcon: StudioTheme.Constants.cube_medium
tooltip: qsTr("Select preview model.") Image {
onClicked: modelMenu.popup() id: materialPreview
}
} width: root.width
height: Math.min(materialPreview.width * 0.75, 400)
anchors.centerIn: parent
fillMode: Image.PreserveAspectFit
source: "image://materialEditor/preview"
cache: false
smooth: true
sourceSize.width: materialPreview.width
sourceSize.height: materialPreview.height
} }
} }
HelperWidgets.Section { HelperWidgets.Section {
// Section with hidden header is used so properties are aligned with the other sections' properties // Section with hidden header is used so properties are aligned with the other sections' properties
hideHeader: true hideHeader: true
width: parent.width Layout.fillWidth: true
collapsible: false collapsible: false
HelperWidgets.SectionLayout { HelperWidgets.SectionLayout {

View File

@@ -844,6 +844,7 @@ extend_qtc_plugin(QmlDesigner
SOURCES SOURCES
materialeditorcontextobject.cpp materialeditorcontextobject.h materialeditorcontextobject.cpp materialeditorcontextobject.h
materialeditordynamicpropertiesproxymodel.cpp materialeditordynamicpropertiesproxymodel.h materialeditordynamicpropertiesproxymodel.cpp materialeditordynamicpropertiesproxymodel.h
materialeditorimageprovider.cpp materialeditorimageprovider.h
materialeditorqmlbackend.cpp materialeditorqmlbackend.h materialeditorqmlbackend.cpp materialeditorqmlbackend.h
materialeditortransaction.cpp materialeditortransaction.h materialeditortransaction.cpp materialeditortransaction.h
materialeditorview.cpp materialeditorview.h materialeditorview.cpp materialeditorview.h

View File

@@ -335,7 +335,14 @@ void MaterialBrowserView::selectedNodesChanged(const QList<ModelNode> &selectedN
void MaterialBrowserView::modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap) void MaterialBrowserView::modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap)
{ {
if (isMaterial(node)) if (!isMaterial(node))
return;
// There might be multiple requests for different preview pixmap sizes.
// Here only the one with the default size is picked.
const double ratio = externalDependencies().formEditorDevicePixelRatio();
const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio;
if (pixmap.width() == dim && pixmap.height() == dim)
m_widget->updateMaterialPreview(node, pixmap); m_widget->updateMaterialPreview(node, pixmap);
} }

View File

@@ -0,0 +1,79 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "materialeditorimageprovider.h"
#include "materialeditorview.h"
#include <QImage>
#include <QTimer>
namespace QmlDesigner {
MaterialEditorImageProvider::MaterialEditorImageProvider(MaterialEditorView *materialEditorView)
: QQuickImageProvider(Pixmap)
, m_delayTimer(new QTimer(this))
{
m_delayTimer->setInterval(500);
m_delayTimer->setSingleShot(true);
m_delayTimer->callOnTimeout([this] {
if (m_previewPixmap.size() != m_requestedSize)
emit this->requestPreview(m_requestedSize);
});
connect(this,
&MaterialEditorImageProvider::requestPreview,
materialEditorView,
&MaterialEditorView::handlePreviewSizeChanged);
}
void MaterialEditorImageProvider::setPixmap(const QPixmap &pixmap)
{
m_previewPixmap = pixmap;
}
QPixmap MaterialEditorImageProvider::requestPixmap(const QString &id,
QSize *size,
const QSize &requestedSize)
{
static QPixmap defaultPreview = QPixmap::fromImage(
QImage(":/materialeditor/images/defaultmaterialpreview.png"));
QPixmap pixmap{150, 150};
if (id == "preview") {
if (!m_previewPixmap.isNull()) {
pixmap = m_previewPixmap;
setRequestedSize(requestedSize);
} else {
pixmap = defaultPreview.scaled(requestedSize, Qt::KeepAspectRatio);
}
} else {
qWarning() << __FUNCTION__ << "Unsupported image id:" << id;
pixmap.fill(Qt::red);
}
if (size)
*size = pixmap.size();
return pixmap;
}
/*!
* \internal
* \brief Sets the requested size. If the requested size is not the same as the
* size of the m_previewPixmap, it will ask \l {MaterialEditorView} to provide
* an image with the requested size
* The requests are delayed until the requested size is stable.
*/
void MaterialEditorImageProvider::setRequestedSize(const QSize &requestedSize)
{
if (!requestedSize.isValid())
return;
m_requestedSize = requestedSize;
if (m_previewPixmap.size() != requestedSize)
m_delayTimer->start();
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,37 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QQuickImageProvider>
QT_BEGIN_NAMESPACE
class QTimer;
QT_END_NAMESPACE
namespace QmlDesigner {
class MaterialEditorView;
class MaterialEditorImageProvider : public QQuickImageProvider
{
Q_OBJECT
public:
explicit MaterialEditorImageProvider(MaterialEditorView *materialEditorView);
void setPixmap(const QPixmap &pixmap);
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
signals:
void requestPreview(QSize);
private:
void setRequestedSize(const QSize &requestedSize);
QPixmap m_previewPixmap;
QSize m_requestedSize;
QTimer *m_delayTimer = nullptr; // Delays the preview requests
};
} // namespace QmlDesigner

View File

@@ -3,9 +3,10 @@
#include "materialeditorqmlbackend.h" #include "materialeditorqmlbackend.h"
#include "propertyeditorvalue.h"
#include "materialeditortransaction.h"
#include "materialeditorcontextobject.h" #include "materialeditorcontextobject.h"
#include "materialeditorimageprovider.h"
#include "materialeditortransaction.h"
#include "propertyeditorvalue.h"
#include <qmldesignerconstants.h> #include <qmldesignerconstants.h>
#include <qmltimeline.h> #include <qmltimeline.h>
@@ -22,7 +23,6 @@
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
#include <QQuickImageProvider>
#include <QQuickItem> #include <QQuickItem>
#include <QQuickWidget> #include <QQuickWidget>
#include <QVector2D> #include <QVector2D>
@@ -39,50 +39,11 @@ static QObject *variantToQObject(const QVariant &value)
namespace QmlDesigner { namespace QmlDesigner {
class MaterialEditorImageProvider : public QQuickImageProvider
{
QPixmap m_previewPixmap;
public:
MaterialEditorImageProvider()
: QQuickImageProvider(Pixmap) {}
void setPixmap(const QPixmap &pixmap)
{
m_previewPixmap = pixmap;
}
QPixmap requestPixmap(const QString &id,
QSize *size,
[[maybe_unused]] const QSize &requestedSize) override
{
static QPixmap defaultPreview = QPixmap::fromImage(QImage(":/materialeditor/images/defaultmaterialpreview.png"));
QPixmap pixmap{150, 150};
if (id == "preview") {
if (!m_previewPixmap.isNull())
pixmap = m_previewPixmap;
else
pixmap = defaultPreview;
} else {
qWarning() << __FUNCTION__ << "Unsupported image id:" << id;
pixmap.fill(Qt::red);
}
if (size)
*size = pixmap.size();
return pixmap;
}
};
MaterialEditorQmlBackend::MaterialEditorQmlBackend(MaterialEditorView *materialEditor) MaterialEditorQmlBackend::MaterialEditorQmlBackend(MaterialEditorView *materialEditor)
: m_quickWidget(Utils::makeUniqueObjectPtr<QQuickWidget>()) : m_quickWidget(Utils::makeUniqueObjectPtr<QQuickWidget>())
, m_materialEditorTransaction(std::make_unique<MaterialEditorTransaction>(materialEditor)) , m_materialEditorTransaction(std::make_unique<MaterialEditorTransaction>(materialEditor))
, m_contextObject(std::make_unique<MaterialEditorContextObject>(m_quickWidget.get())) , m_contextObject(std::make_unique<MaterialEditorContextObject>(m_quickWidget.get()))
, m_materialEditorImageProvider(new MaterialEditorImageProvider()) , m_materialEditorImageProvider(new MaterialEditorImageProvider(materialEditor))
{ {
m_quickWidget->setObjectName(Constants::OBJECT_NAME_MATERIAL_EDITOR); m_quickWidget->setObjectName(Constants::OBJECT_NAME_MATERIAL_EDITOR);
m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);

View File

@@ -525,6 +525,15 @@ void MaterialEditorView::handlePreviewModelChanged(const QString &modelStr)
emitCustomNotification("refresh_material_browser", {}); emitCustomNotification("refresh_material_browser", {});
} }
void MaterialEditorView::handlePreviewSizeChanged(const QSizeF &size)
{
if (m_previewSize == size.toSize())
return;
m_previewSize = size.toSize();
requestPreviewRender();
}
void MaterialEditorView::setupQmlBackend() void MaterialEditorView::setupQmlBackend()
{ {
#ifdef QDS_USE_PROJECTSTORAGE #ifdef QDS_USE_PROJECTSTORAGE
@@ -851,7 +860,9 @@ void MaterialEditorView::propertiesAboutToBeRemoved(const QList<AbstractProperty
void MaterialEditorView::requestPreviewRender() void MaterialEditorView::requestPreviewRender()
{ {
if (model() && model()->nodeInstanceView() && m_selectedMaterial.isValid()) if (model() && model()->nodeInstanceView() && m_selectedMaterial.isValid())
model()->nodeInstanceView()->previewImageDataForGenericNode(m_selectedMaterial, {}); model()->nodeInstanceView()->previewImageDataForGenericNode(m_selectedMaterial,
{},
m_previewSize);
} }
bool MaterialEditorView::hasWidget() const bool MaterialEditorView::hasWidget() const
@@ -937,8 +948,13 @@ void MaterialEditorView::rootNodeTypeChanged(const QString &type, int, int)
void MaterialEditorView::modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap) void MaterialEditorView::modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap)
{ {
if (node == m_selectedMaterial) if (node != m_selectedMaterial)
m_qmlBackEnd->updateMaterialPreview(pixmap); return;
if (m_previewSize.isValid() && pixmap.size() != m_previewSize)
return;
m_qmlBackEnd->updateMaterialPreview(pixmap);
} }
void MaterialEditorView::importsChanged([[maybe_unused]] const Imports &addedImports, void MaterialEditorView::importsChanged([[maybe_unused]] const Imports &addedImports,

View File

@@ -8,6 +8,7 @@
#include <QHash> #include <QHash>
#include <QPointer> #include <QPointer>
#include <QSize>
#include <QTimer> #include <QTimer>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@@ -83,6 +84,7 @@ public slots:
void handleToolBarAction(int action); void handleToolBarAction(int action);
void handlePreviewEnvChanged(const QString &envAndValue); void handlePreviewEnvChanged(const QString &envAndValue);
void handlePreviewModelChanged(const QString &modelStr); void handlePreviewModelChanged(const QString &modelStr);
void handlePreviewSizeChanged(const QSizeF &size);
protected: protected:
void timerEvent(QTimerEvent *event) override; void timerEvent(QTimerEvent *event) override;
@@ -124,6 +126,7 @@ private:
bool m_hasQuick3DImport = false; bool m_hasQuick3DImport = false;
bool m_hasMaterialRoot = false; bool m_hasMaterialRoot = false;
bool m_initializingPreviewData = false; bool m_initializingPreviewData = false;
QSize m_previewSize;
QPointer<QColorDialog> m_colorDialog; QPointer<QColorDialog> m_colorDialog;
QPointer<ItemLibraryInfo> m_itemLibraryInfo; QPointer<ItemLibraryInfo> m_itemLibraryInfo;

View File

@@ -366,7 +366,12 @@ void NavigatorView::enableWidget()
void NavigatorView::modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap) void NavigatorView::modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap)
{ {
m_treeModel->updateToolTipPixmap(node, pixmap); // There might be multiple requests for different preview pixmap sizes.
// Here only the one with the default size is picked.
const double ratio = externalDependencies().formEditorDevicePixelRatio();
const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio;
if (pixmap.width() == dim && pixmap.height() == dim)
m_treeModel->updateToolTipPixmap(node, pixmap);
} }
ModelNode NavigatorView::modelNodeForIndex(const QModelIndex &modelIndex) const ModelNode NavigatorView::modelNodeForIndex(const QModelIndex &modelIndex) const

View File

@@ -134,13 +134,16 @@ public:
void sendInputEvent(QEvent *e) const; void sendInputEvent(QEvent *e) const;
void view3DAction(View3DActionType type, const QVariant &value) override; void view3DAction(View3DActionType type, const QVariant &value) override;
void requestModelNodePreviewImage(const ModelNode &node, const ModelNode &renderNode) const; void requestModelNodePreviewImage(const ModelNode &node,
const ModelNode &renderNode,
const QSize &size = {}) const;
void edit3DViewResized(const QSize &size) const; void edit3DViewResized(const QSize &size) const;
void handlePuppetToCreatorCommand(const PuppetToCreatorCommand &command) override; void handlePuppetToCreatorCommand(const PuppetToCreatorCommand &command) override;
QVariant previewImageDataForGenericNode(const ModelNode &modelNode, QVariant previewImageDataForGenericNode(const ModelNode &modelNode,
const ModelNode &renderNode) const; const ModelNode &renderNode,
const QSize &size = {}) const;
QVariant previewImageDataForImageNode(const ModelNode &modelNode) const; QVariant previewImageDataForImageNode(const ModelNode &modelNode) const;
void setCrashCallback(std::function<void()> crashCallback) void setCrashCallback(std::function<void()> crashCallback)

View File

@@ -1778,9 +1778,6 @@ void NodeInstanceView::handlePuppetToCreatorCommand(const PuppetToCreatorCommand
auto node = modelNodeForInternalId(container.instanceId()); auto node = modelNodeForInternalId(container.instanceId());
if (node.isValid()) { if (node.isValid()) {
const double ratio = m_externalDependencies.formEditorDevicePixelRatio(); const double ratio = m_externalDependencies.formEditorDevicePixelRatio();
const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio;
if (image.height() != dim || image.width() != dim)
image = image.scaled(dim, dim, Qt::KeepAspectRatio);
image.setDevicePixelRatio(ratio); image.setDevicePixelRatio(ratio);
updatePreviewImageForNode(node, image); updatePreviewImageForNode(node, image);
} }
@@ -1826,13 +1823,15 @@ void NodeInstanceView::view3DAction(View3DActionType type, const QVariant &value
} }
void NodeInstanceView::requestModelNodePreviewImage(const ModelNode &node, void NodeInstanceView::requestModelNodePreviewImage(const ModelNode &node,
const ModelNode &renderNode) const const ModelNode &renderNode,
const QSize &size) const
{ {
if (m_nodeInstanceServer && node.isValid() && hasInstanceForModelNode(node)) { if (m_nodeInstanceServer && node.isValid() && hasInstanceForModelNode(node)) {
auto instance = instanceForModelNode(node); auto instance = instanceForModelNode(node);
if (instance.isValid()) { if (instance.isValid()) {
qint32 renderItemId = -1; qint32 renderItemId = -1;
QString componentPath; QString componentPath;
QSize imageSize;
if (renderNode.isValid()) { if (renderNode.isValid()) {
auto renderInstance = instanceForModelNode(renderNode); auto renderInstance = instanceForModelNode(renderNode);
if (renderInstance.isValid()) if (renderInstance.isValid())
@@ -1842,11 +1841,17 @@ void NodeInstanceView::requestModelNodePreviewImage(const ModelNode &node,
} else if (node.isComponent()) { } else if (node.isComponent()) {
componentPath = ModelUtils::componentFilePath(node); componentPath = ModelUtils::componentFilePath(node);
} }
const double ratio = m_externalDependencies.formEditorDevicePixelRatio();
const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio; if (size.isValid()) {
m_nodeInstanceServer->requestModelNodePreviewImage( imageSize = size;
RequestModelNodePreviewImageCommand(instance.instanceId(), QSize(dim, dim), } else {
componentPath, renderItemId)); const double ratio = m_externalDependencies.formEditorDevicePixelRatio();
const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio;
imageSize = {dim, dim};
}
m_nodeInstanceServer->requestModelNodePreviewImage(RequestModelNodePreviewImageCommand(
instance.instanceId(), imageSize, componentPath, renderItemId));
} }
} }
} }
@@ -1991,7 +1996,8 @@ void NodeInstanceView::endNanotrace()
} }
QVariant NodeInstanceView::previewImageDataForGenericNode(const ModelNode &modelNode, QVariant NodeInstanceView::previewImageDataForGenericNode(const ModelNode &modelNode,
const ModelNode &renderNode) const const ModelNode &renderNode,
const QSize &size) const
{ {
if (!modelNode.isValid()) if (!modelNode.isValid())
return {}; return {};
@@ -2006,9 +2012,15 @@ QVariant NodeInstanceView::previewImageDataForGenericNode(const ModelNode &model
} else { } else {
imageData.type = QString::fromLatin1(createQualifiedTypeName(modelNode)); imageData.type = QString::fromLatin1(createQualifiedTypeName(modelNode));
imageData.id = id; imageData.id = id;
m_imageDataMap.insert(id, imageData);
// There might be multiple requests for different preview pixmap sizes.
// Here only the one with the default size is stored.
const double ratio = externalDependencies().formEditorDevicePixelRatio();
const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio;
if (size.width() == dim && size.height() == dim)
m_imageDataMap.insert(id, imageData);
} }
requestModelNodePreviewImage(modelNode, renderNode); requestModelNodePreviewImage(modelNode, renderNode, size);
return modelNodePreviewImageDataToVariant(imageData); return modelNodePreviewImageDataToVariant(imageData);
} }
@@ -2028,7 +2040,6 @@ void NodeInstanceView::updateWatcher(const QString &path)
QStringList oldDirs; QStringList oldDirs;
QStringList newFiles; QStringList newFiles;
QStringList newDirs; QStringList newDirs;
QStringList qsbFiles;
const QString projPath = m_externalDependencies.currentProjectDirPath(); const QString projPath = m_externalDependencies.currentProjectDirPath();