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 StudioTheme as StudioTheme
Column {
ColumnLayout {
id: root
property string previewEnv
@@ -18,7 +18,7 @@ Column {
property StudioTheme.ControlStyle buttonStyle: StudioTheme.ViewBarButtonStyle {
//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
}
@@ -118,16 +118,16 @@ Column {
}
}
Item {
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
height: previewRect.height
Row {
id: optionsToolbar
Layout.preferredHeight: 40
Layout.fillWidth: true
leftPadding: root.__horizontalSpacing
StudioControls.AbstractButton {
id: pinButton
x: root.__horizontalSpacing
style: root.buttonStyle
iconSize: StudioTheme.Values.bigFont
buttonIcon: pinButton.checked ? StudioTheme.Constants.pin : StudioTheme.Constants.unpin
@@ -136,56 +136,53 @@ Column {
onCheckedChanged: itemPane.headerDocked = pinButton.checked
}
Rectangle {
id: previewRect
anchors.horizontalCenter: parent.horizontalCenter
width: 152
height: 152
color: "#000000"
Image {
id: materialPreview
width: 150
height: 150
anchors.centerIn: parent
source: "image://materialEditor/preview"
cache: false
smooth: true
}
HelperWidgets.AbstractButton {
style: root.buttonStyle
buttonIcon: StudioTheme.Constants.textures_medium
tooltip: qsTr("Select preview environment.")
onClicked: envMenu.popup()
}
Item {
id: previewOptions
width: 40
height: previewRect.height
anchors.top: previewRect.top
anchors.left: previewRect.right
anchors.leftMargin: root.__horizontalSpacing
HelperWidgets.AbstractButton {
style: root.buttonStyle
buttonIcon: StudioTheme.Constants.cube_medium
tooltip: qsTr("Select preview model.")
onClicked: modelMenu.popup()
}
}
Column {
anchors.horizontalCenter: parent.horizontalCenter
Rectangle {
id: previewRect
HelperWidgets.AbstractButton {
style: root.buttonStyle
buttonIcon: StudioTheme.Constants.textures_medium
tooltip: qsTr("Select preview environment.")
onClicked: envMenu.popup()
}
Layout.fillWidth: true
Layout.minimumWidth: 152
implicitHeight: materialPreview.height
HelperWidgets.AbstractButton {
style: root.buttonStyle
buttonIcon: StudioTheme.Constants.cube_medium
tooltip: qsTr("Select preview model.")
onClicked: modelMenu.popup()
}
}
clip: true
color: "#000000"
Image {
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 {
// Section with hidden header is used so properties are aligned with the other sections' properties
hideHeader: true
width: parent.width
Layout.fillWidth: true
collapsible: false
HelperWidgets.SectionLayout {

View File

@@ -844,6 +844,7 @@ extend_qtc_plugin(QmlDesigner
SOURCES
materialeditorcontextobject.cpp materialeditorcontextobject.h
materialeditordynamicpropertiesproxymodel.cpp materialeditordynamicpropertiesproxymodel.h
materialeditorimageprovider.cpp materialeditorimageprovider.h
materialeditorqmlbackend.cpp materialeditorqmlbackend.h
materialeditortransaction.cpp materialeditortransaction.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)
{
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);
}

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 "propertyeditorvalue.h"
#include "materialeditortransaction.h"
#include "materialeditorcontextobject.h"
#include "materialeditorimageprovider.h"
#include "materialeditortransaction.h"
#include "propertyeditorvalue.h"
#include <qmldesignerconstants.h>
#include <qmltimeline.h>
@@ -22,7 +23,6 @@
#include <QDir>
#include <QFileInfo>
#include <QQuickImageProvider>
#include <QQuickItem>
#include <QQuickWidget>
#include <QVector2D>
@@ -39,50 +39,11 @@ static QObject *variantToQObject(const QVariant &value)
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)
: m_quickWidget(Utils::makeUniqueObjectPtr<QQuickWidget>())
, m_materialEditorTransaction(std::make_unique<MaterialEditorTransaction>(materialEditor))
, 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->setResizeMode(QQuickWidget::SizeRootObjectToView);

View File

@@ -525,6 +525,15 @@ void MaterialEditorView::handlePreviewModelChanged(const QString &modelStr)
emitCustomNotification("refresh_material_browser", {});
}
void MaterialEditorView::handlePreviewSizeChanged(const QSizeF &size)
{
if (m_previewSize == size.toSize())
return;
m_previewSize = size.toSize();
requestPreviewRender();
}
void MaterialEditorView::setupQmlBackend()
{
#ifdef QDS_USE_PROJECTSTORAGE
@@ -851,7 +860,9 @@ void MaterialEditorView::propertiesAboutToBeRemoved(const QList<AbstractProperty
void MaterialEditorView::requestPreviewRender()
{
if (model() && model()->nodeInstanceView() && m_selectedMaterial.isValid())
model()->nodeInstanceView()->previewImageDataForGenericNode(m_selectedMaterial, {});
model()->nodeInstanceView()->previewImageDataForGenericNode(m_selectedMaterial,
{},
m_previewSize);
}
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)
{
if (node == m_selectedMaterial)
m_qmlBackEnd->updateMaterialPreview(pixmap);
if (node != m_selectedMaterial)
return;
if (m_previewSize.isValid() && pixmap.size() != m_previewSize)
return;
m_qmlBackEnd->updateMaterialPreview(pixmap);
}
void MaterialEditorView::importsChanged([[maybe_unused]] const Imports &addedImports,

View File

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

View File

@@ -366,7 +366,12 @@ void NavigatorView::enableWidget()
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

View File

@@ -134,13 +134,16 @@ public:
void sendInputEvent(QEvent *e) const;
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 handlePuppetToCreatorCommand(const PuppetToCreatorCommand &command) override;
QVariant previewImageDataForGenericNode(const ModelNode &modelNode,
const ModelNode &renderNode) const;
const ModelNode &renderNode,
const QSize &size = {}) const;
QVariant previewImageDataForImageNode(const ModelNode &modelNode) const;
void setCrashCallback(std::function<void()> crashCallback)

View File

@@ -1778,9 +1778,6 @@ void NodeInstanceView::handlePuppetToCreatorCommand(const PuppetToCreatorCommand
auto node = modelNodeForInternalId(container.instanceId());
if (node.isValid()) {
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);
updatePreviewImageForNode(node, image);
}
@@ -1826,13 +1823,15 @@ void NodeInstanceView::view3DAction(View3DActionType type, const QVariant &value
}
void NodeInstanceView::requestModelNodePreviewImage(const ModelNode &node,
const ModelNode &renderNode) const
const ModelNode &renderNode,
const QSize &size) const
{
if (m_nodeInstanceServer && node.isValid() && hasInstanceForModelNode(node)) {
auto instance = instanceForModelNode(node);
if (instance.isValid()) {
qint32 renderItemId = -1;
QString componentPath;
QSize imageSize;
if (renderNode.isValid()) {
auto renderInstance = instanceForModelNode(renderNode);
if (renderInstance.isValid())
@@ -1842,11 +1841,17 @@ void NodeInstanceView::requestModelNodePreviewImage(const ModelNode &node,
} else if (node.isComponent()) {
componentPath = ModelUtils::componentFilePath(node);
}
const double ratio = m_externalDependencies.formEditorDevicePixelRatio();
const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio;
m_nodeInstanceServer->requestModelNodePreviewImage(
RequestModelNodePreviewImageCommand(instance.instanceId(), QSize(dim, dim),
componentPath, renderItemId));
if (size.isValid()) {
imageSize = size;
} else {
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,
const ModelNode &renderNode) const
const ModelNode &renderNode,
const QSize &size) const
{
if (!modelNode.isValid())
return {};
@@ -2006,9 +2012,15 @@ QVariant NodeInstanceView::previewImageDataForGenericNode(const ModelNode &model
} else {
imageData.type = QString::fromLatin1(createQualifiedTypeName(modelNode));
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);
}
@@ -2028,7 +2040,6 @@ void NodeInstanceView::updateWatcher(const QString &path)
QStringList oldDirs;
QStringList newFiles;
QStringList newDirs;
QStringList qsbFiles;
const QString projPath = m_externalDependencies.currentProjectDirPath();