QmlDesigner: Allow drag-n-drop a texture to the 3D Editor

Task-number: QDS-8207
Change-Id: I58bf2857e2ae1830b1dc48f352088c424d4e7c0d
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
Mahmoud Badri
2022-11-15 21:36:09 +02:00
parent f6d878f037
commit 56fa49d8e6
6 changed files with 312 additions and 22 deletions

View File

@@ -0,0 +1,153 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuickDesignerTheme
import HelperWidgets
import StudioControls as StudioControls
import StudioTheme as StudioTheme
Rectangle {
id: root
color: StudioTheme.Values.themePanelBackground
Column {
id: col
padding: 5
spacing: 5
Row {
spacing: 5
Column {
spacing: 5
Text {
text: qsTr("Select material:")
font.bold: true
font.pixelSize: StudioTheme.Values.myFontSize
color: StudioTheme.Values.themeTextColor
}
ListView {
id: materialsListView
width: root.width * .5 - 5
height: root.height - 60
focus: true
clip: true
boundsBehavior: Flickable.StopAtBounds
ScrollBar.vertical: StudioControls.ScrollBar {
visible: materialsListView.height < materialsListView.contentHeight
}
model: materialsModel
delegate: Rectangle {
width: materialsListView.width
height: 20
color: ListView.isCurrentItem ? StudioTheme.Values.themeTextSelectionColor
: "transparent"
function id() {
return modelData.match(/\((.*)\)/).pop()
}
Text {
text: modelData
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: StudioTheme.Values.myFontSize
color: StudioTheme.Values.themeTextColor
leftPadding: 5
}
MouseArea {
anchors.fill: parent
onClicked: {
materialsListView.currentIndex = index
rootView.updatePropsModel(id())
}
}
}
}
}
Column {
spacing: 5
Text {
text: qsTr("Select property:")
font.bold: true
font.pixelSize: StudioTheme.Values.myFontSize
color: StudioTheme.Values.themeTextColor
}
ListView {
id: propertiesListView
width: root.width * .5 - 5
height: root.height - 60
focus: true
clip: true
boundsBehavior: Flickable.StopAtBounds
ScrollBar.vertical: StudioControls.ScrollBar {
visible: propertiesListView.height < propertiesListView.contentHeight
}
model: propertiesModel
delegate: Rectangle {
width: propertiesListView.width
height: 20
color: ListView.isCurrentItem ? StudioTheme.Values.themeTextSelectionColor
: "transparent"
function propName() {
return modelData
}
Text {
text: modelData
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: StudioTheme.Values.myFontSize
color: StudioTheme.Values.themeTextColor
leftPadding: 5
}
MouseArea {
anchors.fill: parent
onClicked: {
propertiesListView.currentIndex = index
}
}
}
}
}
}
Row {
spacing: 5
anchors.right: parent.right
anchors.rightMargin: 10
Button {
text: qsTr("Cancel")
onClicked: {
rootView.closeChooseMatPropsView()
}
}
Button {
text: qsTr("Apply")
onClicked: {
let matId = materialsListView.currentItem.id()
let prop = propertiesListView.currentItem.propName()
rootView.applyTextureToMaterial(matId, prop)
}
}
}
}
}

View File

@@ -2,33 +2,48 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "edit3dview.h" #include "edit3dview.h"
#include "backgroundcolorselection.h" #include "backgroundcolorselection.h"
#include "bindingproperty.h"
#include "designersettings.h"
#include "designmodecontext.h"
#include "edit3dactions.h" #include "edit3dactions.h"
#include "edit3dcanvas.h" #include "edit3dcanvas.h"
#include "edit3dviewconfig.h" #include "edit3dviewconfig.h"
#include "edit3dwidget.h" #include "edit3dwidget.h"
#include "materialbrowserwidget.h"
#include "metainfo.h" #include "metainfo.h"
#include "nodehints.h" #include "nodehints.h"
#include "nodeinstanceview.h"
#include "qmldesignerconstants.h"
#include "qmldesignericons.h"
#include "qmldesignerplugin.h"
#include "seekerslider.h" #include "seekerslider.h"
#include "variantproperty.h"
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/messagebox.h> #include <coreplugin/messagebox.h>
#include <designeractionmanager.h>
#include <designersettings.h>
#include <designmodecontext.h>
#include <nodeinstanceview.h>
#include <viewmanager.h>
#include <qmldesignerconstants.h>
#include <qmldesignericons.h>
#include <qmldesignerplugin.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/utilsicons.h> #include <utils/utilsicons.h>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickView>
#include <QToolButton> #include <QToolButton>
namespace QmlDesigner { namespace QmlDesigner {
static QString propertyEditorResourcesPath()
{
#ifdef SHARE_QML_PATH
if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources";
#endif
return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString();
}
Edit3DView::Edit3DView(ExternalDependenciesInterface &externalDependencies) Edit3DView::Edit3DView(ExternalDependenciesInterface &externalDependencies)
: AbstractView{externalDependencies} : AbstractView{externalDependencies}
{ {
@@ -261,6 +276,24 @@ void Edit3DView::customNotification([[maybe_unused]] const AbstractView *view,
resetPuppet(); resetPuppet();
} }
bool Edit3DView::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Escape) {
if (obj == m_chooseMatPropsView)
m_chooseMatPropsView->close();
}
} else if (event->type() == QEvent::Close) {
if (obj == m_chooseMatPropsView) {
m_droppedModelNode = {};
m_chooseMatPropsView->deleteLater();
}
}
return AbstractView::eventFilter(obj, event);
}
/** /**
* @brief Get node at position from puppet process * @brief Get node at position from puppet process
* *
@@ -278,15 +311,64 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos
setSelectedModelNode(modelNode); setSelectedModelNode(modelNode);
m_edit3DWidget->showContextMenu(m_contextMenuPos, modelNode, pos3d); m_edit3DWidget->showContextMenu(m_contextMenuPos, modelNode, pos3d);
} else if (m_nodeAtPosReqType == NodeAtPosReqType::MaterialDrop) { } else if (m_nodeAtPosReqType == NodeAtPosReqType::MaterialDrop) {
const bool isModel = modelNode.metaInfo().isQtQuick3DModel(); bool isModel = modelNode.metaInfo().isQtQuick3DModel();
if (m_droppedMaterial.isValid() && modelNode.isValid() && isModel) { if (m_droppedModelNode.isValid() && modelNode.isValid() && isModel) {
executeInTransaction(__FUNCTION__, [&] { executeInTransaction(__FUNCTION__, [&] {
assignMaterialTo3dModel(modelNode, m_droppedMaterial); assignMaterialTo3dModel(modelNode, m_droppedModelNode);
}); });
} }
} else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleMaterialDrop) { } else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleMaterialDrop) {
emitCustomNotification("drop_bundle_material", {modelNode}); // To ContentLibraryView emitCustomNotification("drop_bundle_material", {modelNode}); // To ContentLibraryView
} else if (m_nodeAtPosReqType == NodeAtPosReqType::TextureDrop) {
if (m_droppedModelNode.isValid() && modelNode.isValid() && modelNode.metaInfo().isQtQuick3DModel()) {
// get model's material list
BindingProperty matsProp = modelNode.bindingProperty("materials");
QList<ModelNode> materials;
if (hasId(matsProp.expression()))
materials.append(modelNodeForId(matsProp.expression()));
else
materials = matsProp.resolveToModelNodeList();
if (materials.size() > 0) {
m_textureModels.clear();
QStringList materialsModel;
for (const ModelNode &mat : std::as_const(materials)) {
QString matName = mat.variantProperty("objectName").value().toString();
materialsModel.append(QLatin1String("%1 (%2)").arg(matName, mat.id()));
QList<PropertyName> texProps;
for (const PropertyMetaInfo &p : mat.metaInfo().properties()) {
if (p.propertyType().isQtQuick3DTexture())
texProps.append(p.name());
}
m_textureModels.insert(mat.id(), texProps);
}
QString path = MaterialBrowserWidget::qmlSourcesPath() + "/ChooseMaterialProperty.qml";
m_chooseMatPropsView = new QQuickView;
m_chooseMatPropsView->setTitle(tr("Select a material property"));
m_chooseMatPropsView->setResizeMode(QQuickView::SizeRootObjectToView);
m_chooseMatPropsView->setMinimumSize({150, 100});
m_chooseMatPropsView->setMaximumSize({600, 400});
m_chooseMatPropsView->setWidth(450);
m_chooseMatPropsView->setHeight(300);
m_chooseMatPropsView->setFlags(Qt::Widget);
m_chooseMatPropsView->setModality(Qt::ApplicationModal);
m_chooseMatPropsView->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
m_chooseMatPropsView->rootContext()->setContextProperties({
{"rootView", QVariant::fromValue(this)},
{"materialsModel", QVariant::fromValue(materialsModel)},
{"propertiesModel", QVariant::fromValue(m_textureModels.value(materials.at(0).id()))},
});
m_chooseMatPropsView->setSource(QUrl::fromLocalFile(path));
m_chooseMatPropsView->installEventFilter(this);
m_chooseMatPropsView->show();
}
}
} }
if (m_nodeAtPosReqType != NodeAtPosReqType::TextureDrop)
m_droppedModelNode = {};
m_nodeAtPosReqType = NodeAtPosReqType::None; m_nodeAtPosReqType = NodeAtPosReqType::None;
} }
@@ -842,7 +924,7 @@ void Edit3DView::startContextMenu(const QPoint &pos)
void Edit3DView::dropMaterial(const ModelNode &matNode, const QPointF &pos) void Edit3DView::dropMaterial(const ModelNode &matNode, const QPointF &pos)
{ {
m_nodeAtPosReqType = NodeAtPosReqType::MaterialDrop; m_nodeAtPosReqType = NodeAtPosReqType::MaterialDrop;
m_droppedMaterial = matNode; m_droppedModelNode = matNode;
emitView3DAction(View3DActionType::GetNodeAtPos, pos); emitView3DAction(View3DActionType::GetNodeAtPos, pos);
} }
@@ -852,4 +934,37 @@ void Edit3DView::dropBundleMaterial(const QPointF &pos)
emitView3DAction(View3DActionType::GetNodeAtPos, pos); emitView3DAction(View3DActionType::GetNodeAtPos, pos);
} }
void Edit3DView::dropTexture(const ModelNode &textureNode, const QPointF &pos)
{
m_nodeAtPosReqType = NodeAtPosReqType::TextureDrop;
m_droppedModelNode = textureNode;
emitView3DAction(View3DActionType::GetNodeAtPos, pos);
}
void Edit3DView::updatePropsModel(const QString &matId)
{
m_chooseMatPropsView->rootContext()->setContextProperty("propertiesModel",
QVariant::fromValue(m_textureModels.value(matId)));
}
void Edit3DView::applyTextureToMaterial(const QString &matId, const QString &propName)
{
QTC_ASSERT(m_droppedModelNode.isValid(), return);
ModelNode mat = modelNodeForId(matId);
QTC_ASSERT(mat.isValid(), return);
BindingProperty texProp = mat.bindingProperty(propName.toLatin1());
QTC_ASSERT(texProp.isValid(), return);
texProp.setExpression(m_droppedModelNode.id());
closeChooseMatPropsView();
}
void Edit3DView::closeChooseMatPropsView()
{
m_chooseMatPropsView->close();
}
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -8,14 +8,16 @@
#include <modelcache.h> #include <modelcache.h>
#include <QImage> #include <QImage>
#include <QPointer>
#include <QSize> #include <QSize>
#include <QTimer> #include <QTimer>
#include <QVariant> #include <QVariant>
#include <QVector> #include <QVector>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QInputEvent;
class QAction; class QAction;
class QInputEvent;
class QQuickView;
QT_END_NAMESPACE QT_END_NAMESPACE
namespace QmlDesigner { namespace QmlDesigner {
@@ -62,6 +64,14 @@ public:
void startContextMenu(const QPoint &pos); void startContextMenu(const QPoint &pos);
void dropMaterial(const ModelNode &matNode, const QPointF &pos); void dropMaterial(const ModelNode &matNode, const QPointF &pos);
void dropBundleMaterial(const QPointF &pos); void dropBundleMaterial(const QPointF &pos);
void dropTexture(const ModelNode &textureNode, const QPointF &pos);
Q_INVOKABLE void updatePropsModel(const QString &matId);
Q_INVOKABLE void applyTextureToMaterial(const QString &matId, const QString &propName);
Q_INVOKABLE void closeChooseMatPropsView();
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
private slots: private slots:
void onEntriesChanged(); void onEntriesChanged();
@@ -70,6 +80,7 @@ private:
enum class NodeAtPosReqType { enum class NodeAtPosReqType {
BundleMaterialDrop, BundleMaterialDrop,
MaterialDrop, MaterialDrop,
TextureDrop,
ContextMenu, ContextMenu,
None None
}; };
@@ -112,10 +123,12 @@ private:
SeekerSlider *m_seeker = nullptr; SeekerSlider *m_seeker = nullptr;
int particlemode; int particlemode;
ModelCache<QImage> m_canvasCache; ModelCache<QImage> m_canvasCache;
ModelNode m_droppedMaterial; ModelNode m_droppedModelNode;
NodeAtPosReqType m_nodeAtPosReqType; NodeAtPosReqType m_nodeAtPosReqType;
QPoint m_contextMenuPos; QPoint m_contextMenuPos;
QTimer m_compressionTimer; QTimer m_compressionTimer;
QPointer<QQuickView> m_chooseMatPropsView;
QHash<QString, QList<PropertyName>> m_textureModels;
}; };
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -427,7 +427,8 @@ void Edit3DWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent)
->viewManager().designerActionManager(); ->viewManager().designerActionManager();
if (actionManager.externalDragHasSupportedAssets(dragEnterEvent->mimeData()) if (actionManager.externalDragHasSupportedAssets(dragEnterEvent->mimeData())
|| dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL) || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL)
|| dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL)) { || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL)
|| dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_TEXTURE)) {
dragEnterEvent->acceptProposedAction(); dragEnterEvent->acceptProposedAction();
} }
} }
@@ -436,15 +437,23 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent)
{ {
const QPointF pos = m_canvas->mapFrom(this, dropEvent->position()); const QPointF pos = m_canvas->mapFrom(this, dropEvent->position());
// handle dropping materials // handle dropping materials and textures
if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL)) { if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL)
QByteArray data = dropEvent->mimeData()->data(Constants::MIME_TYPE_MATERIAL); || dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_TEXTURE)) {
bool isMaterial = dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL);
QByteArray data = dropEvent->mimeData()->data(isMaterial
? QString::fromLatin1(Constants::MIME_TYPE_MATERIAL)
: QString::fromLatin1(Constants::MIME_TYPE_TEXTURE));
QDataStream stream(data); QDataStream stream(data);
qint32 internalId; qint32 internalId;
stream >> internalId; stream >> internalId;
if (ModelNode matNode = m_view->modelNodeForInternalId(internalId)) if (ModelNode dropNode = m_view->modelNodeForInternalId(internalId)) {
m_view->dropMaterial(matNode, pos); if (isMaterial)
m_view->dropMaterial(dropNode, pos);
else
m_view->dropTexture(dropNode, pos);
}
return; return;
} }

View File

@@ -134,7 +134,7 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event)
QByteArray data; QByteArray data;
QMimeData *mimeData = new QMimeData; QMimeData *mimeData = new QMimeData;
QDataStream stream(&data, QIODevice::WriteOnly); QDataStream stream(&data, QIODevice::WriteOnly);
stream << m_materialToDrag.internalId(); stream << (isMaterial ? m_materialToDrag.internalId() : m_textureToDrag.internalId());
mimeData->setData(isMaterial ? QString::fromLatin1(Constants::MIME_TYPE_MATERIAL) mimeData->setData(isMaterial ? QString::fromLatin1(Constants::MIME_TYPE_MATERIAL)
: QString::fromLatin1(Constants::MIME_TYPE_TEXTURE), : QString::fromLatin1(Constants::MIME_TYPE_TEXTURE),
data); data);

View File

@@ -1149,7 +1149,7 @@ bool MaterialEditorView::eventFilter(QObject *obj, QEvent *event)
if (m_qmlBackEnd && m_qmlBackEnd->widget() == obj) if (m_qmlBackEnd && m_qmlBackEnd->widget() == obj)
QMetaObject::invokeMethod(m_qmlBackEnd->widget()->rootObject(), "closeContextMenu"); QMetaObject::invokeMethod(m_qmlBackEnd->widget()->rootObject(), "closeContextMenu");
} }
return QObject::eventFilter(obj, event); return AbstractView::eventFilter(obj, event);
} }
void MaterialEditorView::reloadQml() void MaterialEditorView::reloadQml()