diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml
index 4f73722f692..b0861aa882e 100644
--- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml
+++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml
@@ -42,5 +42,6 @@ Rectangle {
sourceSize.height: root.height - 10
anchors.centerIn: parent
cache: false
+ smooth: true
}
}
diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml
index 73e9865c194..b98f7293f3b 100644
--- a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml
+++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml
@@ -140,6 +140,7 @@ Column {
anchors.centerIn: parent
source: "image://materialEditor/preview"
cache: false
+ smooth: true
}
}
diff --git a/share/qtcreator/qmldesigner/textureEditorQmlSource/EmptyTextureEditorPane.qml b/share/qtcreator/qmldesigner/textureEditorQmlSource/EmptyTextureEditorPane.qml
new file mode 100644
index 00000000000..b6527e082d7
--- /dev/null
+++ b/share/qtcreator/qmldesigner/textureEditorQmlSource/EmptyTextureEditorPane.qml
@@ -0,0 +1,50 @@
+// 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 2.15
+import QtQuick.Layouts 1.15
+import QtQuickDesignerTheme 1.0
+import HelperWidgets 2.0
+import StudioTheme 1.0 as StudioTheme
+
+PropertyEditorPane {
+ id: root
+
+ signal toolBarAction(int action)
+
+ // Called from C++, dummy method to avoid warnings
+ function closeContextMenu() {}
+
+ Column {
+ id: col
+
+ TextureEditorToolBar {
+ width: root.width
+
+ onToolBarAction: (action) => root.toolBarAction(action)
+ }
+
+ Item {
+ width: root.width - 2 * col.padding
+ height: 150
+
+ Text {
+ text: {
+ if (!hasQuick3DImport)
+ qsTr("To use Texture Editor, first add the QtQuick3D module in the Components view.")
+ else if (!hasMaterialLibrary)
+ qsTr("Texture Editor is disabled inside a non-visual component.")
+ else
+ qsTr("There are no textures in this project.
Select '+' to create one.")
+ }
+ textFormat: Text.RichText
+ color: StudioTheme.Values.themeTextColor
+ font.pixelSize: StudioTheme.Values.mediumFontSize
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.WordWrap
+ width: root.width
+ anchors.centerIn: parent
+ }
+ }
+ }
+}
diff --git a/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorPane.qml b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorPane.qml
new file mode 100644
index 00000000000..30e21478226
--- /dev/null
+++ b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorPane.qml
@@ -0,0 +1,65 @@
+// 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 2.15
+import QtQuickDesignerTheme 1.0
+import HelperWidgets 2.0
+
+PropertyEditorPane {
+ id: itemPane
+
+ signal toolBarAction(int action)
+
+ // invoked from C++ to refresh material preview image
+ function refreshPreview()
+ {
+ topSection.refreshPreview()
+ }
+
+ // Called also from C++ to close context menu on focus out
+ function closeContextMenu()
+ {
+ // Nothing
+ }
+
+ TextureEditorTopSection {
+ id: topSection
+
+ onToolBarAction: (action) => itemPane.toolBarAction(action)
+ }
+
+ Item { width: 1; height: 10 }
+
+ DynamicPropertiesSection {
+ propertiesModel: TextureEditorDynamicPropertiesModel {}
+ }
+
+ Loader {
+ id: specificsTwo
+
+ property string theSource: specificQmlData
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+ visible: theSource !== ""
+ sourceComponent: specificQmlComponent
+
+ onTheSourceChanged: {
+ active = false
+ active = true
+ }
+ }
+
+ Item {
+ width: 1
+ height: 10
+ visible: specificsTwo.visible
+ }
+
+ Loader {
+ id: specificsOne
+ anchors.left: parent.left
+ anchors.right: parent.right
+ source: specificsUrl
+ }
+}
diff --git a/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml
new file mode 100644
index 00000000000..91b46c5da47
--- /dev/null
+++ b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml
@@ -0,0 +1,69 @@
+// 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 2.15
+import QtQuickDesignerTheme 1.0
+import HelperWidgets 2.0
+import StudioTheme 1.0 as StudioTheme
+import ToolBarAction 1.0
+
+Rectangle {
+ id: root
+
+ color: StudioTheme.Values.themeSectionHeadBackground
+ width: row.width
+ height: 40
+
+ signal toolBarAction(int action)
+
+ Row {
+ id: row
+
+ anchors.verticalCenter: parent.verticalCenter
+ leftPadding: 6
+
+ IconButton {
+ icon: StudioTheme.Constants.applyMaterialToSelected
+
+ normalColor: StudioTheme.Values.themeSectionHeadBackground
+ iconSize: StudioTheme.Values.bigIconFontSize
+ buttonSize: root.height
+ enabled: hasTexture && hasModelSelection && hasQuick3DImport && hasMaterialLibrary
+ onClicked: root.toolBarAction(ToolBarAction.ApplyToSelected)
+ tooltip: qsTr("Apply texture to selected model's material.")
+ }
+
+ IconButton {
+ icon: StudioTheme.Constants.newMaterial
+
+ normalColor: StudioTheme.Values.themeSectionHeadBackground
+ iconSize: StudioTheme.Values.bigIconFontSize
+ buttonSize: root.height
+ enabled: hasQuick3DImport && hasMaterialLibrary
+ onClicked: root.toolBarAction(ToolBarAction.AddNewTexture)
+ tooltip: qsTr("Create new texture.")
+ }
+
+ IconButton {
+ icon: StudioTheme.Constants.deleteMaterial
+
+ normalColor: StudioTheme.Values.themeSectionHeadBackground
+ iconSize: StudioTheme.Values.bigIconFontSize
+ buttonSize: root.height
+ enabled: hasTexture && hasQuick3DImport && hasMaterialLibrary
+ onClicked: root.toolBarAction(ToolBarAction.DeleteCurrentTexture)
+ tooltip: qsTr("Delete current texture.")
+ }
+
+ IconButton {
+ icon: StudioTheme.Constants.openMaterialBrowser
+
+ normalColor: StudioTheme.Values.themeSectionHeadBackground
+ iconSize: StudioTheme.Values.bigIconFontSize
+ buttonSize: root.height
+ enabled: hasQuick3DImport && hasMaterialLibrary
+ onClicked: root.toolBarAction(ToolBarAction.OpenMaterialBrowser)
+ tooltip: qsTr("Open material browser.")
+ }
+ }
+}
diff --git a/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorTopSection.qml b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorTopSection.qml
new file mode 100644
index 00000000000..287278d9548
--- /dev/null
+++ b/share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorTopSection.qml
@@ -0,0 +1,45 @@
+// 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
+
+Column {
+ id: root
+
+ signal toolBarAction(int action)
+
+ function refreshPreview()
+ {
+ texturePreview.source = ""
+ texturePreview.source = "image://textureEditor/" + backendValues.source.valueToString
+ }
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ TextureEditorToolBar {
+ width: root.width
+
+ onToolBarAction: (action) => root.toolBarAction(action)
+ }
+
+ Item { width: 1; height: 10 } // spacer
+
+ Rectangle {
+ id: previewRect
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: 152
+ height: 152
+ color: "#000000"
+
+ Image {
+ id: texturePreview
+
+ sourceSize.width: 150
+ sourceSize.height: 150
+ anchors.centerIn: parent
+ source: "image://textureEditor/" + backendValues.source.valueToString
+ cache: false
+ }
+ }
+}
diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt
index 4ef26740a80..c2c3c941b2b 100644
--- a/src/plugins/qmldesigner/CMakeLists.txt
+++ b/src/plugins/qmldesigner/CMakeLists.txt
@@ -410,6 +410,7 @@ add_qtc_plugin(QmlDesigner
${CMAKE_CURRENT_LIST_DIR}/components/itemlibrary
${CMAKE_CURRENT_LIST_DIR}/components/materialbrowser
${CMAKE_CURRENT_LIST_DIR}/components/materialeditor
+ ${CMAKE_CURRENT_LIST_DIR}/components/textureeditor
${CMAKE_CURRENT_LIST_DIR}/components/navigator
${CMAKE_CURRENT_LIST_DIR}/components/propertyeditor
${CMAKE_CURRENT_LIST_DIR}/components/stateseditor
@@ -814,6 +815,17 @@ extend_qtc_plugin(QmlDesigner
materialeditor.qrc
)
+extend_qtc_plugin(QmlDesigner
+ SOURCES_PREFIX components/textureeditor
+ SOURCES
+ textureeditorcontextobject.cpp textureeditorcontextobject.h
+ textureeditordynamicpropertiesproxymodel.cpp textureeditordynamicpropertiesproxymodel.h
+ textureeditorqmlbackend.cpp textureeditorqmlbackend.h
+ textureeditortransaction.cpp textureeditortransaction.h
+ textureeditorview.cpp textureeditorview.h
+ textureeditor.qrc
+)
+
extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/materialbrowser
SOURCES
diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp
index 5a06b2b1e83..08ee673ff16 100644
--- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
#include
#include
@@ -69,6 +70,7 @@ public:
, propertyEditorView(imageCache, externalDependencies)
, materialEditorView{externalDependencies}
, materialBrowserView{externalDependencies}
+ , textureEditorView{externalDependencies}
, statesEditorView{externalDependencies}
, newStatesEditorView{externalDependencies}
{}
@@ -90,6 +92,7 @@ public:
PropertyEditorView propertyEditorView;
MaterialEditorView materialEditorView;
MaterialBrowserView materialBrowserView;
+ TextureEditorView textureEditorView;
StatesEditorView statesEditorView;
Experimental::StatesEditorView newStatesEditorView;
@@ -219,6 +222,7 @@ QList ViewManager::standardViews() const
&d->contentLibraryView,
&d->materialEditorView,
&d->materialBrowserView,
+ &d->textureEditorView,
&d->statesEditorView,
&d->newStatesEditorView, // TODO
&d->designerActionManagerView};
@@ -400,6 +404,7 @@ QList ViewManager::widgetInfos() const
widgetInfoList.append(d->contentLibraryView.widgetInfo());
widgetInfoList.append(d->materialEditorView.widgetInfo());
widgetInfoList.append(d->materialBrowserView.widgetInfo());
+ widgetInfoList.append(d->textureEditorView.widgetInfo());
if (useOldStatesEditor())
widgetInfoList.append(d->statesEditorView.widgetInfo());
else
diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp
index 2eedb474d98..b41977e9cdc 100644
--- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp
+++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp
@@ -274,6 +274,8 @@ void Edit3DView::customNotification([[maybe_unused]] const AbstractView *view,
{
if (identifier == "asset_import_update")
resetPuppet();
+ else if (identifier == "apply_texture_to_model3D")
+ applyTextureToModel3D(nodeList.at(0), nodeList.at(1));
}
bool Edit3DView::eventFilter(QObject *obj, QEvent *event)
@@ -320,51 +322,7 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos
} else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleMaterialDrop) {
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 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 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();
- }
- }
+ applyTextureToModel3D(modelNode, m_droppedModelNode);
}
if (m_nodeAtPosReqType != NodeAtPosReqType::TextureDrop)
@@ -372,6 +330,57 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos
m_nodeAtPosReqType = NodeAtPosReqType::None;
}
+void Edit3DView::applyTextureToModel3D(const ModelNode &model3D, const ModelNode &texture)
+{
+ if (!texture.isValid() || !model3D.isValid() || !model3D.metaInfo().isQtQuick3DModel())
+ return;
+
+ m_droppedModelNode = texture;
+
+ // get model's material list
+ BindingProperty matsProp = model3D.bindingProperty("materials");
+ QList 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 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();
+ }
+}
void Edit3DView::sendInputEvent(QInputEvent *e) const
{
if (nodeInstanceView())
diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h
index be510e45660..2f542c0e714 100644
--- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h
+++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h
@@ -65,6 +65,7 @@ public:
void dropMaterial(const ModelNode &matNode, const QPointF &pos);
void dropBundleMaterial(const QPointF &pos);
void dropTexture(const ModelNode &textureNode, const QPointF &pos);
+ void applyTextureToModel3D(const ModelNode &model3D, const ModelNode &texture);
Q_INVOKABLE void updatePropsModel(const QString &matId);
Q_INVOKABLE void applyTextureToMaterial(const QString &matId, const QString &propName);
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp
index 15613960da1..772620cd04e 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp
@@ -180,6 +180,20 @@ ModelNode MaterialBrowserTexturesModel::textureAt(int idx) const
return {};
}
+bool MaterialBrowserTexturesModel::hasSingleModelSelection() const
+{
+ return m_hasSingleModelSelection;
+}
+
+void MaterialBrowserTexturesModel::setHasSingleModelSelection(bool b)
+{
+ if (b == m_hasSingleModelSelection)
+ return;
+
+ m_hasSingleModelSelection = b;
+ emit hasSingleModelSelectionChanged();
+}
+
void MaterialBrowserTexturesModel::resetModel()
{
beginResetModel();
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h
index 4b5d1bd55bf..f23ca1e48ac 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h
@@ -16,6 +16,8 @@ class MaterialBrowserTexturesModel : public QAbstractListModel
Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged)
+ Q_PROPERTY(bool hasSingleModelSelection READ hasSingleModelSelection
+ WRITE setHasSingleModelSelection NOTIFY hasSingleModelSelectionChanged)
public:
MaterialBrowserTexturesModel(QObject *parent = nullptr);
@@ -35,6 +37,9 @@ public:
int textureIndex(const ModelNode &material) const;
ModelNode textureAt(int idx) const;
+ bool hasSingleModelSelection() const;
+ void setHasSingleModelSelection(bool b);
+
void resetModel();
Q_INVOKABLE void selectTexture(int idx, bool force = false);
@@ -43,6 +48,7 @@ public:
signals:
void isEmptyChanged();
+ void hasSingleModelSelectionChanged();
void materialSectionsChanged();
void selectedIndexChanged(int idx);
void duplicateTextureTriggered(const QmlDesigner::ModelNode &material);
@@ -58,6 +64,7 @@ private:
int m_selectedIndex = 0;
bool m_isEmpty = true;
+ bool m_hasSingleModelSelection = false;
};
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp
index 0f05ed0b39b..5238ae0ed3f 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp
@@ -49,9 +49,9 @@ WidgetInfo MaterialBrowserView::widgetInfo()
auto matEditorContext = new Internal::MaterialBrowserContext(m_widget.data());
Core::ICore::addContextObject(matEditorContext);
- MaterialBrowserModel *matBrowserModel = m_widget->materialBrowserModel().data();
// custom notifications below are sent to the MaterialEditor
+ MaterialBrowserModel *matBrowserModel = m_widget->materialBrowserModel().data();
connect(matBrowserModel, &MaterialBrowserModel::selectedIndexChanged, this, [&] (int idx) {
ModelNode matNode = m_widget->materialBrowserModel()->materialAt(idx);
@@ -139,6 +139,13 @@ WidgetInfo MaterialBrowserView::widgetInfo()
}
});
});
+
+ // custom notifications below are sent to the TextureEditor
+ MaterialBrowserTexturesModel *texturesModel = m_widget->materialBrowserTexturesModel().data();
+ connect(texturesModel, &MaterialBrowserTexturesModel::selectedIndexChanged, this, [&] (int idx) {
+ ModelNode texNode = m_widget->materialBrowserTexturesModel()->textureAt(idx);
+ emitCustomNotification("selected_texture_changed", {texNode}, {});
+ });
}
return createWidgetInfo(m_widget.data(),
@@ -230,6 +237,7 @@ void MaterialBrowserView::selectedNodesChanged(const QList &selectedN
});
m_widget->materialBrowserModel()->setHasModelSelection(!m_selectedModels.isEmpty());
+ m_widget->materialBrowserTexturesModel()->setHasSingleModelSelection(m_selectedModels.size() == 1);
// the logic below selects the material of the first selected model if auto selection is on
if (!m_autoSelectModelMaterial)
@@ -299,20 +307,21 @@ void MaterialBrowserView::nodeReparented(const ModelNode &node,
void MaterialBrowserView::nodeAboutToBeRemoved(const ModelNode &removedNode)
{
- // removing the material editor node
+ // removing the material lib node
if (removedNode.id() == Constants::MATERIAL_LIB_ID) {
m_widget->materialBrowserModel()->setMaterials({}, m_hasQuick3DImport);
m_widget->clearPreviewCache();
return;
}
- // not a material under the material editor
- if (!isMaterial(removedNode)
- || removedNode.parentProperty().parentModelNode().id() != Constants::MATERIAL_LIB_ID) {
+ // not under the material lib
+ if (removedNode.parentProperty().parentModelNode().id() != Constants::MATERIAL_LIB_ID)
return;
- }
- m_widget->materialBrowserModel()->removeMaterial(removedNode);
+ if (isMaterial(removedNode))
+ m_widget->materialBrowserModel()->removeMaterial(removedNode);
+ else if (isTexture(removedNode))
+ m_widget->materialBrowserTexturesModel()->removeTexture(removedNode);
}
void MaterialBrowserView::nodeRemoved([[maybe_unused]] const ModelNode &removedNode,
@@ -323,6 +332,7 @@ void MaterialBrowserView::nodeRemoved([[maybe_unused]] const ModelNode &removedN
return;
m_widget->materialBrowserModel()->updateSelectedMaterial();
+ m_widget->materialBrowserTexturesModel()->updateSelectedTexture();
}
void QmlDesigner::MaterialBrowserView::loadPropertyGroups()
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp
index 0cac19c4988..fe2a9f06ab5 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp
@@ -104,7 +104,7 @@ public:
pixmap = Utils::StyleHelper::dpiSpecificImageFile(id);
if (pixmap.isNull())
- pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/materialeditor/images/texture_default.png");
+ pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png");
if (size)
*size = pixmap.size();
diff --git a/src/plugins/qmldesigner/components/materialeditor/images/texture_default.png b/src/plugins/qmldesigner/components/materialeditor/images/texture_default.png
deleted file mode 100644
index 70e85cc2eba..00000000000
Binary files a/src/plugins/qmldesigner/components/materialeditor/images/texture_default.png and /dev/null differ
diff --git a/src/plugins/qmldesigner/components/materialeditor/images/texture_default@2x.png b/src/plugins/qmldesigner/components/materialeditor/images/texture_default@2x.png
deleted file mode 100644
index a27efb275d3..00000000000
Binary files a/src/plugins/qmldesigner/components/materialeditor/images/texture_default@2x.png and /dev/null differ
diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditor.qrc b/src/plugins/qmldesigner/components/materialeditor/materialeditor.qrc
index 872452df1a0..4ed3c769175 100644
--- a/src/plugins/qmldesigner/components/materialeditor/materialeditor.qrc
+++ b/src/plugins/qmldesigner/components/materialeditor/materialeditor.qrc
@@ -1,7 +1,5 @@
images/defaultmaterialpreview.png
- images/texture_default.png
- images/texture_default@2x.png
diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp
index 97d93bb7b8f..379ccc411ac 100644
--- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp
+++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp
@@ -825,6 +825,12 @@ void MaterialEditorView::variantPropertiesChanged(const QList &
changed = true;
}
+ if (!changed && node.metaInfo().isQtQuick3DTexture()
+ && m_selectedMaterial.bindingProperties().size() > 0) {
+ // update preview when editing texture properties if the material has binding properties
+ changed = true;
+ }
+
dynamicPropertiesModel()->dispatchPropertyChanges(property);
}
if (changed)
diff --git a/src/plugins/qmldesigner/components/textureeditor/images/texture_default.png b/src/plugins/qmldesigner/components/textureeditor/images/texture_default.png
new file mode 100644
index 00000000000..32ad4e3eda6
Binary files /dev/null and b/src/plugins/qmldesigner/components/textureeditor/images/texture_default.png differ
diff --git a/src/plugins/qmldesigner/components/textureeditor/images/texture_default@2x.png b/src/plugins/qmldesigner/components/textureeditor/images/texture_default@2x.png
new file mode 100644
index 00000000000..f1ed622ca58
Binary files /dev/null and b/src/plugins/qmldesigner/components/textureeditor/images/texture_default@2x.png differ
diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditor.qrc b/src/plugins/qmldesigner/components/textureeditor/textureeditor.qrc
new file mode 100644
index 00000000000..680fe7d82b0
--- /dev/null
+++ b/src/plugins/qmldesigner/components/textureeditor/textureeditor.qrc
@@ -0,0 +1,6 @@
+
+
+ images/texture_default.png
+ images/texture_default@2x.png
+
+
diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp
new file mode 100644
index 00000000000..1bfc1c6c6b1
--- /dev/null
+++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp
@@ -0,0 +1,324 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "textureeditorcontextobject.h"
+
+#include "abstractview.h"
+#include "documentmanager.h"
+#include "model.h"
+#include "qmldesignerplugin.h"
+#include "qmlobjectnode.h"
+#include "qmltimeline.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+namespace QmlDesigner {
+
+TextureEditorContextObject::TextureEditorContextObject(QQmlContext *context, QObject *parent)
+ : QObject(parent)
+ , m_qmlContext(context)
+{
+ qmlRegisterUncreatableType("ToolBarAction", 1, 0, "ToolBarAction", "Enum type");
+}
+
+QQmlComponent *TextureEditorContextObject::specificQmlComponent()
+{
+ if (m_specificQmlComponent)
+ return m_specificQmlComponent;
+
+ m_specificQmlComponent = new QQmlComponent(m_qmlContext->engine(), this);
+ m_specificQmlComponent->setData(m_specificQmlData.toUtf8(), QUrl::fromLocalFile("specifics.qml"));
+
+ return m_specificQmlComponent;
+}
+
+QString TextureEditorContextObject::convertColorToString(const QVariant &color)
+{
+ QString colorString;
+ QColor theColor;
+ if (color.canConvert(QVariant::Color)) {
+ theColor = color.value();
+ } else if (color.canConvert(QVariant::Vector3D)) {
+ auto vec = color.value();
+ theColor = QColor::fromRgbF(vec.x(), vec.y(), vec.z());
+ }
+
+ colorString = theColor.name();
+
+ if (theColor.alpha() != 255) {
+ QString hexAlpha = QString("%1").arg(theColor.alpha(), 2, 16, QLatin1Char('0'));
+ colorString.remove(0, 1);
+ colorString.prepend(hexAlpha);
+ colorString.prepend(QStringLiteral("#"));
+ }
+ return colorString;
+}
+
+// TODO: this method is used by the ColorEditor helper widget, check if at all needed?
+QColor TextureEditorContextObject::colorFromString(const QString &colorString)
+{
+ return colorString;
+}
+
+void TextureEditorContextObject::insertKeyframe(const QString &propertyName)
+{
+ QTC_ASSERT(m_model && m_model->rewriterView(), return);
+ QTC_ASSERT(m_selectedTexture.isValid(), return);
+
+ // Ideally we should not missuse the rewriterView
+ // If we add more code here we have to forward the material editor view
+ RewriterView *rewriterView = m_model->rewriterView();
+
+ QmlTimeline timeline = rewriterView->currentTimeline();
+
+ QTC_ASSERT(timeline.isValid(), return);
+
+ rewriterView->executeInTransaction("TextureEditorContextObject::insertKeyframe", [&] {
+ timeline.insertKeyframe(m_selectedTexture, propertyName.toUtf8());
+ });
+}
+
+int TextureEditorContextObject::majorVersion() const
+{
+ return m_majorVersion;
+}
+
+void TextureEditorContextObject::setMajorVersion(int majorVersion)
+{
+ if (m_majorVersion == majorVersion)
+ return;
+
+ m_majorVersion = majorVersion;
+
+ emit majorVersionChanged();
+}
+
+bool TextureEditorContextObject::hasActiveTimeline() const
+{
+ return m_hasActiveTimeline;
+}
+
+void TextureEditorContextObject::setHasActiveTimeline(bool b)
+{
+ if (b == m_hasActiveTimeline)
+ return;
+
+ m_hasActiveTimeline = b;
+ emit hasActiveTimelineChanged();
+}
+
+bool TextureEditorContextObject::hasQuick3DImport() const
+{
+ return m_hasQuick3DImport;
+}
+
+void TextureEditorContextObject::setHasQuick3DImport(bool b)
+{
+ if (b == m_hasQuick3DImport)
+ return;
+
+ m_hasQuick3DImport = b;
+ emit hasQuick3DImportChanged();
+}
+
+bool TextureEditorContextObject::hasMaterialLibrary() const
+{
+ return m_hasMaterialLibrary;
+}
+
+void TextureEditorContextObject::setHasMaterialLibrary(bool b)
+{
+ if (b == m_hasMaterialLibrary)
+ return;
+
+ m_hasMaterialLibrary = b;
+ emit hasMaterialLibraryChanged();
+}
+
+bool TextureEditorContextObject::hasModelSelection() const
+{
+ return m_hasModelSelection;
+}
+
+void TextureEditorContextObject::setHasModelSelection(bool b)
+{
+ if (b == m_hasModelSelection)
+ return;
+
+ m_hasModelSelection = b;
+ emit hasModelSelectionChanged();
+}
+
+void TextureEditorContextObject::setSelectedMaterial(const ModelNode &matNode)
+{
+ m_selectedTexture = matNode;
+}
+
+void TextureEditorContextObject::setSpecificsUrl(const QUrl &newSpecificsUrl)
+{
+ if (newSpecificsUrl == m_specificsUrl)
+ return;
+
+ m_specificsUrl = newSpecificsUrl;
+ emit specificsUrlChanged();
+}
+
+void TextureEditorContextObject::setSpecificQmlData(const QString &newSpecificQmlData)
+{
+ if (newSpecificQmlData == m_specificQmlData)
+ return;
+
+ m_specificQmlData = newSpecificQmlData;
+
+ delete m_specificQmlComponent;
+ m_specificQmlComponent = nullptr;
+
+ emit specificQmlComponentChanged();
+ emit specificQmlDataChanged();
+}
+
+void TextureEditorContextObject::setStateName(const QString &newStateName)
+{
+ if (newStateName == m_stateName)
+ return;
+
+ m_stateName = newStateName;
+ emit stateNameChanged();
+}
+
+void TextureEditorContextObject::setAllStateNames(const QStringList &allStates)
+{
+ if (allStates == m_allStateNames)
+ return;
+
+ m_allStateNames = allStates;
+ emit allStateNamesChanged();
+}
+
+void TextureEditorContextObject::setIsBaseState(bool newIsBaseState)
+{
+ if (newIsBaseState == m_isBaseState)
+ return;
+
+ m_isBaseState = newIsBaseState;
+ emit isBaseStateChanged();
+}
+
+void TextureEditorContextObject::setSelectionChanged(bool newSelectionChanged)
+{
+ if (newSelectionChanged == m_selectionChanged)
+ return;
+
+ m_selectionChanged = newSelectionChanged;
+ emit selectionChangedChanged();
+}
+
+void TextureEditorContextObject::setBackendValues(QQmlPropertyMap *newBackendValues)
+{
+ if (newBackendValues == m_backendValues)
+ return;
+
+ m_backendValues = newBackendValues;
+ emit backendValuesChanged();
+}
+
+void TextureEditorContextObject::setModel(Model *model)
+{
+ m_model = model;
+}
+
+void TextureEditorContextObject::triggerSelectionChanged()
+{
+ setSelectionChanged(!m_selectionChanged);
+}
+
+void TextureEditorContextObject::setHasAliasExport(bool hasAliasExport)
+{
+ if (m_aliasExport == hasAliasExport)
+ return;
+
+ m_aliasExport = hasAliasExport;
+ emit hasAliasExportChanged();
+}
+
+void TextureEditorContextObject::hideCursor()
+{
+ if (QApplication::overrideCursor())
+ return;
+
+ QApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
+
+ if (QWidget *w = QApplication::activeWindow())
+ m_lastPos = QCursor::pos(w->screen());
+}
+
+void TextureEditorContextObject::restoreCursor()
+{
+ if (!QApplication::overrideCursor())
+ return;
+
+ QApplication::restoreOverrideCursor();
+
+ if (QWidget *w = QApplication::activeWindow())
+ QCursor::setPos(w->screen(), m_lastPos);
+}
+
+void TextureEditorContextObject::holdCursorInPlace()
+{
+ if (!QApplication::overrideCursor())
+ return;
+
+ if (QWidget *w = QApplication::activeWindow())
+ QCursor::setPos(w->screen(), m_lastPos);
+}
+
+int TextureEditorContextObject::devicePixelRatio()
+{
+ if (QWidget *w = QApplication::activeWindow())
+ return w->devicePixelRatio();
+
+ return 1;
+}
+
+QStringList TextureEditorContextObject::allStatesForId(const QString &id)
+{
+ if (m_model && m_model->rewriterView()) {
+ const QmlObjectNode node = m_model->rewriterView()->modelNodeForId(id);
+ if (node.isValid())
+ return node.allStateNames();
+ }
+
+ return {};
+}
+
+bool TextureEditorContextObject::isBlocked(const QString &propName) const
+{
+ if (!m_selectedTexture.isValid())
+ return false;
+
+ if (!m_model || !m_model->rewriterView())
+ return false;
+
+ if (QmlObjectNode(m_selectedTexture).isBlocked(propName.toUtf8()))
+ return true;
+
+ return false;
+}
+
+void TextureEditorContextObject::goIntoComponent()
+{
+ QTC_ASSERT(m_model, return);
+ DocumentManager::goIntoComponent(m_selectedTexture);
+}
+
+} // QmlDesigner
diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h
new file mode 100644
index 00000000000..c2537589a95
--- /dev/null
+++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h
@@ -0,0 +1,156 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace QmlDesigner {
+
+class Model;
+
+class TextureEditorContextObject : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QUrl specificsUrl READ specificsUrl WRITE setSpecificsUrl NOTIFY specificsUrlChanged)
+ Q_PROPERTY(QString specificQmlData READ specificQmlData WRITE setSpecificQmlData NOTIFY specificQmlDataChanged)
+ Q_PROPERTY(QQmlComponent *specificQmlComponent READ specificQmlComponent NOTIFY specificQmlComponentChanged)
+
+ Q_PROPERTY(QString stateName READ stateName WRITE setStateName NOTIFY stateNameChanged)
+ Q_PROPERTY(QStringList allStateNames READ allStateNames WRITE setAllStateNames NOTIFY allStateNamesChanged)
+
+ Q_PROPERTY(bool isBaseState READ isBaseState WRITE setIsBaseState NOTIFY isBaseStateChanged)
+ Q_PROPERTY(bool selectionChanged READ selectionChanged WRITE setSelectionChanged NOTIFY selectionChangedChanged)
+
+ Q_PROPERTY(int majorVersion READ majorVersion WRITE setMajorVersion NOTIFY majorVersionChanged)
+
+ Q_PROPERTY(bool hasAliasExport READ hasAliasExport NOTIFY hasAliasExportChanged)
+ Q_PROPERTY(bool hasActiveTimeline READ hasActiveTimeline NOTIFY hasActiveTimelineChanged)
+ Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged)
+ Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged)
+ Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary WRITE setHasMaterialLibrary NOTIFY hasMaterialLibraryChanged)
+
+ Q_PROPERTY(QQmlPropertyMap *backendValues READ backendValues WRITE setBackendValues NOTIFY backendValuesChanged)
+
+public:
+ TextureEditorContextObject(QQmlContext *context, QObject *parent = nullptr);
+
+ QUrl specificsUrl() const { return m_specificsUrl; }
+ QString specificQmlData() const {return m_specificQmlData; }
+ QQmlComponent *specificQmlComponent();
+ QString stateName() const { return m_stateName; }
+ QStringList allStateNames() const { return m_allStateNames; }
+
+ bool isBaseState() const { return m_isBaseState; }
+ bool selectionChanged() const { return m_selectionChanged; }
+
+ QQmlPropertyMap *backendValues() const { return m_backendValues; }
+
+ Q_INVOKABLE QString convertColorToString(const QVariant &color);
+ Q_INVOKABLE QColor colorFromString(const QString &colorString);
+
+ Q_INVOKABLE void insertKeyframe(const QString &propertyName);
+
+ Q_INVOKABLE void hideCursor();
+ Q_INVOKABLE void restoreCursor();
+ Q_INVOKABLE void holdCursorInPlace();
+
+ Q_INVOKABLE int devicePixelRatio();
+
+ Q_INVOKABLE QStringList allStatesForId(const QString &id);
+
+ Q_INVOKABLE bool isBlocked(const QString &propName) const;
+ Q_INVOKABLE void goIntoComponent();
+
+ enum ToolBarAction {
+ ApplyToSelected,
+ AddNewTexture,
+ DeleteCurrentTexture,
+ OpenMaterialBrowser
+ };
+ Q_ENUM(ToolBarAction)
+
+ int majorVersion() const;
+ void setMajorVersion(int majorVersion);
+
+ bool hasActiveTimeline() const;
+ void setHasActiveTimeline(bool b);
+
+ bool hasQuick3DImport() const;
+ void setHasQuick3DImport(bool b);
+
+ bool hasMaterialLibrary() const;
+ void setHasMaterialLibrary(bool b);
+
+ bool hasModelSelection() const;
+ void setHasModelSelection(bool b);
+
+ bool hasAliasExport() const { return m_aliasExport; }
+
+ void setSelectedMaterial(const ModelNode &matNode);
+
+ void setSpecificsUrl(const QUrl &newSpecificsUrl);
+ void setSpecificQmlData(const QString &newSpecificQmlData);
+ void setStateName(const QString &newStateName);
+ void setAllStateNames(const QStringList &allStates);
+ void setIsBaseState(bool newIsBaseState);
+ void setSelectionChanged(bool newSelectionChanged);
+ void setBackendValues(QQmlPropertyMap *newBackendValues);
+ void setModel(Model *model);
+
+ void triggerSelectionChanged();
+ void setHasAliasExport(bool hasAliasExport);
+
+signals:
+ void specificsUrlChanged();
+ void specificQmlDataChanged();
+ void specificQmlComponentChanged();
+ void stateNameChanged();
+ void allStateNamesChanged();
+ void isBaseStateChanged();
+ void selectionChangedChanged();
+ void backendValuesChanged();
+ void majorVersionChanged();
+ void hasAliasExportChanged();
+ void hasActiveTimelineChanged();
+ void hasQuick3DImportChanged();
+ void hasMaterialLibraryChanged();
+ void hasModelSelectionChanged();
+
+private:
+ QUrl m_specificsUrl;
+ QString m_specificQmlData;
+ QQmlComponent *m_specificQmlComponent = nullptr;
+ QQmlContext *m_qmlContext = nullptr;
+
+ QString m_stateName;
+ QStringList m_allStateNames;
+
+ int m_majorVersion = 1;
+
+ QQmlPropertyMap *m_backendValues = nullptr;
+ Model *m_model = nullptr;
+
+ QPoint m_lastPos;
+
+ bool m_isBaseState = false;
+ bool m_selectionChanged = false;
+ bool m_aliasExport = false;
+ bool m_hasActiveTimeline = false;
+ bool m_hasQuick3DImport = false;
+ bool m_hasMaterialLibrary = false;
+ bool m_hasModelSelection = false;
+
+ ModelNode m_selectedTexture;
+};
+
+} // QmlDesigner
diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.cpp
new file mode 100644
index 00000000000..e6f9c64c740
--- /dev/null
+++ b/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.cpp
@@ -0,0 +1,22 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "textureeditordynamicpropertiesproxymodel.h"
+
+#include "dynamicpropertiesmodel.h"
+#include "textureeditorview.h"
+
+using namespace QmlDesigner;
+
+TextureEditorDynamicPropertiesProxyModel::TextureEditorDynamicPropertiesProxyModel(QObject *parent)
+ : DynamicPropertiesProxyModel(parent)
+{
+ if (TextureEditorView::instance())
+ initModel(TextureEditorView::instance()->dynamicPropertiesModel());
+}
+
+void TextureEditorDynamicPropertiesProxyModel::registerDeclarativeType()
+{
+ DynamicPropertiesProxyModel::registerDeclarativeType();
+ qmlRegisterType("HelperWidgets", 2, 0, "TextureEditorDynamicPropertiesModel");
+}
diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.h b/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.h
new file mode 100644
index 00000000000..5cd81b751f2
--- /dev/null
+++ b/src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.h
@@ -0,0 +1,16 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "dynamicpropertiesproxymodel.h"
+
+class TextureEditorDynamicPropertiesProxyModel : public DynamicPropertiesProxyModel
+{
+ Q_OBJECT
+
+public:
+ explicit TextureEditorDynamicPropertiesProxyModel(QObject *parent = nullptr);
+
+ static void registerDeclarativeType();
+};
diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp
new file mode 100644
index 00000000000..b385afe4f37
--- /dev/null
+++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp
@@ -0,0 +1,310 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "textureeditorqmlbackend.h"
+
+#include "bindingproperty.h"
+#include "documentmanager.h"
+#include "nodemetainfo.h"
+#include "propertyeditorvalue.h"
+#include "qmldesignerconstants.h"
+#include "qmlobjectnode.h"
+#include "qmltimeline.h"
+#include "textureeditortransaction.h"
+#include "textureeditorcontextobject.h"
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static QObject *variantToQObject(const QVariant &value)
+{
+ if (value.userType() == QMetaType::QObjectStar || value.userType() > QMetaType::User)
+ return *(QObject **)value.constData();
+
+ return nullptr;
+}
+
+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)
+ : m_view(new QQuickWidget)
+ , m_textureEditorTransaction(new TextureEditorTransaction(textureEditor))
+ , m_contextObject(new TextureEditorContextObject(m_view->rootContext()))
+ , m_textureEditorImageProvider(new TextureEditorImageProvider())
+{
+ m_view->setResizeMode(QQuickWidget::SizeRootObjectToView);
+ m_view->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
+ m_view->engine()->addImageProvider("textureEditor", m_textureEditorImageProvider);
+ m_contextObject->setBackendValues(&m_backendValuesPropertyMap);
+ m_contextObject->setModel(textureEditor->model());
+ context()->setContextObject(m_contextObject.data());
+
+ QObject::connect(&m_backendValuesPropertyMap, &DesignerPropertyMap::valueChanged,
+ textureEditor, &TextureEditorView::changeValue);
+}
+
+TextureEditorQmlBackend::~TextureEditorQmlBackend()
+{
+}
+
+PropertyName TextureEditorQmlBackend::auxNamePostFix(const PropertyName &propertyName)
+{
+ return propertyName + "__AUX";
+}
+
+void TextureEditorQmlBackend::createPropertyEditorValue(const QmlObjectNode &qmlObjectNode,
+ const PropertyName &name,
+ const QVariant &value,
+ TextureEditorView *textureEditor)
+{
+ PropertyName propertyName(name);
+ propertyName.replace('.', '_');
+ auto valueObject = qobject_cast(variantToQObject(backendValuesPropertyMap().value(QString::fromUtf8(propertyName))));
+ if (!valueObject) {
+ valueObject = new PropertyEditorValue(&backendValuesPropertyMap());
+ QObject::connect(valueObject, &PropertyEditorValue::valueChanged, &backendValuesPropertyMap(), &DesignerPropertyMap::valueChanged);
+ QObject::connect(valueObject, &PropertyEditorValue::expressionChanged, textureEditor, &TextureEditorView::changeExpression);
+ QObject::connect(valueObject, &PropertyEditorValue::exportPropertyAsAliasRequested, textureEditor, &TextureEditorView::exportPropertyAsAlias);
+ QObject::connect(valueObject, &PropertyEditorValue::removeAliasExportRequested, textureEditor, &TextureEditorView::removeAliasExport);
+ backendValuesPropertyMap().insert(QString::fromUtf8(propertyName), QVariant::fromValue(valueObject));
+ }
+ valueObject->setName(name);
+ valueObject->setModelNode(qmlObjectNode);
+
+ if (qmlObjectNode.propertyAffectedByCurrentState(name) && !(qmlObjectNode.modelNode().property(name).isBindingProperty()))
+ valueObject->setValue(qmlObjectNode.modelValue(name));
+ else
+ valueObject->setValue(value);
+
+ if (propertyName != "id" && qmlObjectNode.currentState().isBaseState()
+ && qmlObjectNode.modelNode().property(propertyName).isBindingProperty()) {
+ valueObject->setExpression(qmlObjectNode.modelNode().bindingProperty(propertyName).expression());
+ } else {
+ if (qmlObjectNode.hasBindingProperty(name))
+ valueObject->setExpression(qmlObjectNode.expression(name));
+ else
+ valueObject->setExpression(qmlObjectNode.instanceValue(name).toString());
+ }
+}
+
+void TextureEditorQmlBackend::setValue(const QmlObjectNode &, const PropertyName &name, const QVariant &value)
+{
+ // Vector*D values need to be split into their subcomponents
+ if (value.type() == QVariant::Vector2D) {
+ const char *suffix[2] = {"_x", "_y"};
+ auto vecValue = value.value();
+ for (int i = 0; i < 2; ++i) {
+ PropertyName subPropName(name.size() + 2, '\0');
+ subPropName.replace(0, name.size(), name);
+ subPropName.replace(name.size(), 2, suffix[i]);
+ auto propertyValue = qobject_cast(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(subPropName))));
+ if (propertyValue)
+ propertyValue->setValue(QVariant(vecValue[i]));
+ }
+ } else if (value.type() == QVariant::Vector3D) {
+ const char *suffix[3] = {"_x", "_y", "_z"};
+ auto vecValue = value.value();
+ for (int i = 0; i < 3; ++i) {
+ PropertyName subPropName(name.size() + 2, '\0');
+ subPropName.replace(0, name.size(), name);
+ subPropName.replace(name.size(), 2, suffix[i]);
+ auto propertyValue = qobject_cast(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(subPropName))));
+ if (propertyValue)
+ propertyValue->setValue(QVariant(vecValue[i]));
+ }
+ } else if (value.type() == QVariant::Vector4D) {
+ const char *suffix[4] = {"_x", "_y", "_z", "_w"};
+ auto vecValue = value.value();
+ for (int i = 0; i < 4; ++i) {
+ PropertyName subPropName(name.size() + 2, '\0');
+ subPropName.replace(0, name.size(), name);
+ subPropName.replace(name.size(), 2, suffix[i]);
+ auto propertyValue = qobject_cast(
+ variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(subPropName))));
+ if (propertyValue)
+ propertyValue->setValue(QVariant(vecValue[i]));
+ }
+ } else {
+ PropertyName propertyName = name;
+ propertyName.replace('.', '_');
+ auto propertyValue = qobject_cast(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(propertyName))));
+ if (propertyValue)
+ propertyValue->setValue(value);
+ }
+}
+
+QQmlContext *TextureEditorQmlBackend::context() const
+{
+ return m_view->rootContext();
+}
+
+TextureEditorContextObject *TextureEditorQmlBackend::contextObject() const
+{
+ return m_contextObject.data();
+}
+
+QQuickWidget *TextureEditorQmlBackend::widget() const
+{
+ return m_view;
+}
+
+void TextureEditorQmlBackend::setSource(const QUrl &url)
+{
+ m_view->setSource(url);
+}
+
+Internal::QmlAnchorBindingProxy &TextureEditorQmlBackend::backendAnchorBinding()
+{
+ return m_backendAnchorBinding;
+}
+
+DesignerPropertyMap &TextureEditorQmlBackend::backendValuesPropertyMap()
+{
+ return m_backendValuesPropertyMap;
+}
+
+TextureEditorTransaction *TextureEditorQmlBackend::textureEditorTransaction() const
+{
+ return m_textureEditorTransaction.data();
+}
+
+PropertyEditorValue *TextureEditorQmlBackend::propertyValueForName(const QString &propertyName)
+{
+ return qobject_cast(variantToQObject(backendValuesPropertyMap().value(propertyName)));
+}
+
+void TextureEditorQmlBackend::setup(const QmlObjectNode &selectedTextureNode, const QString &stateName,
+ const QUrl &qmlSpecificsFile, TextureEditorView *textureEditor)
+{
+ if (selectedTextureNode.isValid()) {
+ m_contextObject->setModel(textureEditor->model());
+
+ for (const auto &property : selectedTextureNode.modelNode().metaInfo().properties()) {
+ createPropertyEditorValue(selectedTextureNode,
+ property.name(),
+ selectedTextureNode.instanceValue(property.name()),
+ textureEditor);
+ }
+
+ // model node
+ m_backendModelNode.setup(selectedTextureNode.modelNode());
+ context()->setContextProperty("modelNodeBackend", &m_backendModelNode);
+ context()->setContextProperty("hasTexture", QVariant(true));
+
+ // className
+ auto valueObject = qobject_cast(variantToQObject(
+ m_backendValuesPropertyMap.value(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY)));
+ if (!valueObject)
+ valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
+ valueObject->setName(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY);
+ valueObject->setModelNode(selectedTextureNode.modelNode());
+ valueObject->setValue(m_backendModelNode.simplifiedTypeName());
+ QObject::connect(valueObject,
+ &PropertyEditorValue::valueChanged,
+ &backendValuesPropertyMap(),
+ &DesignerPropertyMap::valueChanged);
+ m_backendValuesPropertyMap.insert(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY,
+ QVariant::fromValue(valueObject));
+
+ // anchors
+ m_backendAnchorBinding.setup(selectedTextureNode.modelNode());
+ context()->setContextProperties(
+ QVector{
+ {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)},
+ {{"transaction"}, QVariant::fromValue(m_textureEditorTransaction.data())}
+ }
+ );
+
+ contextObject()->setSpecificsUrl(qmlSpecificsFile);
+ contextObject()->setStateName(stateName);
+
+ QStringList stateNames = selectedTextureNode.allStateNames();
+ stateNames.prepend("base state");
+ contextObject()->setAllStateNames(stateNames);
+ contextObject()->setSelectedMaterial(selectedTextureNode);
+ contextObject()->setIsBaseState(selectedTextureNode.isInBaseState());
+ contextObject()->setHasAliasExport(selectedTextureNode.isAliasExported());
+ contextObject()->setHasActiveTimeline(QmlTimeline::hasActiveTimeline(selectedTextureNode.view()));
+
+ contextObject()->setSelectionChanged(false);
+
+ NodeMetaInfo metaInfo = selectedTextureNode.modelNode().metaInfo();
+ contextObject()->setMajorVersion(metaInfo.isValid() ? metaInfo.majorVersion() : -1);
+ } else {
+ context()->setContextProperty("hasTexture", QVariant(false));
+ }
+}
+
+QString TextureEditorQmlBackend::propertyEditorResourcesPath()
+{
+#ifdef SHARE_QML_PATH
+ if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
+ return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources";
+#endif
+ return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString();
+}
+
+void TextureEditorQmlBackend::emitSelectionToBeChanged()
+{
+ m_backendModelNode.emitSelectionToBeChanged();
+}
+
+void TextureEditorQmlBackend::emitSelectionChanged()
+{
+ m_backendModelNode.emitSelectionChanged();
+}
+
+void TextureEditorQmlBackend::setValueforAuxiliaryProperties(const QmlObjectNode &qmlObjectNode,
+ AuxiliaryDataKeyView key)
+{
+ const PropertyName propertyName = auxNamePostFix(PropertyName(key.name));
+ setValue(qmlObjectNode, propertyName, qmlObjectNode.modelNode().auxiliaryDataWithDefault(key));
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h
new file mode 100644
index 00000000000..fab25c6d586
--- /dev/null
+++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "designerpropertymap.h"
+#include "qmlanchorbindingproxy.h"
+#include "qmlmodelnodeproxy.h"
+
+#include
+
+class PropertyEditorValue;
+
+QT_BEGIN_NAMESPACE
+class QQuickWidget;
+QT_END_NAMESPACE
+
+namespace QmlDesigner {
+
+class TextureEditorContextObject;
+class TextureEditorImageProvider;
+class TextureEditorTransaction;
+class TextureEditorView;
+
+class TextureEditorQmlBackend
+{
+ Q_DISABLE_COPY(TextureEditorQmlBackend)
+
+public:
+ TextureEditorQmlBackend(TextureEditorView *materialEditor);
+ ~TextureEditorQmlBackend();
+
+ void setup(const QmlObjectNode &selectedTextureNode, const QString &stateName, const QUrl &qmlSpecificsFile,
+ TextureEditorView *textureEditor);
+ void setValue(const QmlObjectNode &fxObjectNode, const PropertyName &name, const QVariant &value);
+
+ QQmlContext *context() const;
+ TextureEditorContextObject *contextObject() const;
+ QQuickWidget *widget() const;
+ void setSource(const QUrl &url);
+ Internal::QmlAnchorBindingProxy &backendAnchorBinding();
+ DesignerPropertyMap &backendValuesPropertyMap();
+ TextureEditorTransaction *textureEditorTransaction() const;
+
+ PropertyEditorValue *propertyValueForName(const QString &propertyName);
+
+ static QString propertyEditorResourcesPath();
+
+ void emitSelectionToBeChanged();
+ void emitSelectionChanged();
+
+ void setValueforAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, AuxiliaryDataKeyView key);
+
+private:
+ void createPropertyEditorValue(const QmlObjectNode &qmlObjectNode,
+ const PropertyName &name, const QVariant &value,
+ TextureEditorView *textureEditor);
+ PropertyName auxNamePostFix(const PropertyName &propertyName);
+
+ QQuickWidget *m_view = nullptr;
+ Internal::QmlAnchorBindingProxy m_backendAnchorBinding;
+ QmlModelNodeProxy m_backendModelNode;
+ DesignerPropertyMap m_backendValuesPropertyMap;
+ QScopedPointer m_textureEditorTransaction;
+ QScopedPointer m_contextObject;
+ TextureEditorImageProvider *m_textureEditorImageProvider = nullptr;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.cpp
new file mode 100644
index 00000000000..91ca4efb40a
--- /dev/null
+++ b/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.cpp
@@ -0,0 +1,48 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "textureeditortransaction.h"
+
+#include
+
+namespace QmlDesigner {
+
+TextureEditorTransaction::TextureEditorTransaction(TextureEditorView *textureEditor)
+ : QObject(textureEditor),
+ m_textureEditor(textureEditor)
+{
+}
+
+void TextureEditorTransaction::start()
+{
+ if (!m_textureEditor->model())
+ return;
+ if (m_rewriterTransaction.isValid())
+ m_rewriterTransaction.commit();
+ m_rewriterTransaction = m_textureEditor->beginRewriterTransaction(QByteArrayLiteral("MaterialEditorTransaction::start"));
+ m_timerId = startTimer(10000);
+}
+
+void TextureEditorTransaction::end()
+{
+ if (m_rewriterTransaction.isValid() && m_textureEditor->model()) {
+ killTimer(m_timerId);
+ m_rewriterTransaction.commit();
+ }
+}
+
+bool TextureEditorTransaction::active() const
+{
+ return m_rewriterTransaction.isValid();
+}
+
+void TextureEditorTransaction::timerEvent(QTimerEvent *timerEvent)
+{
+ if (timerEvent->timerId() != m_timerId)
+ return;
+ killTimer(timerEvent->timerId());
+ if (m_rewriterTransaction.isValid())
+ m_rewriterTransaction.commit();
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.h b/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.h
new file mode 100644
index 00000000000..6c543aebcbc
--- /dev/null
+++ b/src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "textureeditorview.h"
+
+namespace QmlDesigner {
+
+class TextureEditorTransaction : public QObject
+{
+ Q_OBJECT
+
+public:
+ TextureEditorTransaction(TextureEditorView *textureEditor);
+
+ Q_INVOKABLE void start();
+ Q_INVOKABLE void end();
+
+ Q_INVOKABLE bool active() const;
+
+protected:
+ void timerEvent(QTimerEvent *event) override;
+
+private:
+ TextureEditorView *m_textureEditor = nullptr;
+ RewriterTransaction m_rewriterTransaction;
+ int m_timerId = -1;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp
new file mode 100644
index 00000000000..a753c1d03a6
--- /dev/null
+++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp
@@ -0,0 +1,866 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "textureeditorview.h"
+
+#include "textureeditorqmlbackend.h"
+#include "textureeditorcontextobject.h"
+#include "textureeditordynamicpropertiesproxymodel.h"
+#include "propertyeditorvalue.h"
+#include "textureeditortransaction.h"
+#include "assetslibrarywidget.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace QmlDesigner {
+
+TextureEditorView::TextureEditorView(ExternalDependenciesInterface &externalDependencies)
+ : AbstractView{externalDependencies}
+ , m_stackedWidget(new QStackedWidget)
+ , m_dynamicPropertiesModel(new Internal::DynamicPropertiesModel(true, this))
+{
+ m_updateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F12), m_stackedWidget);
+ connect(m_updateShortcut, &QShortcut::activated, this, &TextureEditorView::reloadQml);
+
+ m_ensureMatLibTimer.callOnTimeout([this] {
+ if (model() && model()->rewriterView() && !model()->rewriterView()->hasIncompleteTypeInformation()
+ && model()->rewriterView()->errors().isEmpty()) {
+ ensureMaterialLibraryNode();
+ if (m_qmlBackEnd && m_qmlBackEnd->contextObject())
+ m_qmlBackEnd->contextObject()->setHasMaterialLibrary(materialLibraryNode().isValid());
+ m_ensureMatLibTimer.stop();
+ }
+ });
+
+ m_stackedWidget->setStyleSheet(Theme::replaceCssColors(
+ QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"))));
+ m_stackedWidget->setMinimumWidth(250);
+ QmlDesignerPlugin::trackWidgetFocusTime(m_stackedWidget, Constants::EVENT_TEXTUREEDITOR_TIME);
+
+ TextureEditorDynamicPropertiesProxyModel::registerDeclarativeType();
+}
+
+TextureEditorView::~TextureEditorView()
+{
+ qDeleteAll(m_qmlBackendHash);
+}
+
+// from texture editor to model
+void TextureEditorView::changeValue(const QString &name)
+{
+ PropertyName propertyName = name.toUtf8();
+
+ if (propertyName.isNull() || locked() || noValidSelection() || propertyName == "id"
+ || propertyName == Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY) {
+ return;
+ }
+
+ PropertyName underscoreName(propertyName);
+ underscoreName.replace('.', '_');
+ PropertyEditorValue *value = m_qmlBackEnd->propertyValueForName(QString::fromLatin1(underscoreName));
+
+ if (!value)
+ return;
+
+ if (propertyName.endsWith("__AUX")) {
+ commitAuxValueToModel(propertyName, value->value());
+ return;
+ }
+
+ const NodeMetaInfo metaInfo = m_selectedTexture.metaInfo();
+
+ QVariant castedValue;
+
+ if (auto property = metaInfo.property(propertyName)) {
+ castedValue = property.castedValue(value->value());
+ } else {
+ qWarning() << __FUNCTION__ << propertyName << "cannot be casted (metainfo)";
+ return;
+ }
+
+ if (value->value().isValid() && !castedValue.isValid()) {
+ qWarning() << __FUNCTION__ << propertyName << "not properly casted (metainfo)";
+ return;
+ }
+
+ bool propertyTypeUrl = false;
+
+ if (auto property = metaInfo.property(propertyName)) {
+ if (property.propertyType().isUrl()) {
+ // turn absolute local file paths into relative paths
+ propertyTypeUrl = true;
+ QString filePath = castedValue.toUrl().toString();
+ QFileInfo fi(filePath);
+ if (fi.exists() && fi.isAbsolute()) {
+ QDir fileDir(QFileInfo(model()->fileUrl().toLocalFile()).absolutePath());
+ castedValue = QUrl(fileDir.relativeFilePath(filePath));
+ }
+ }
+ }
+
+ if (name == "state" && castedValue.toString() == "base state")
+ castedValue = "";
+
+ if (castedValue.type() == QVariant::Color) {
+ QColor color = castedValue.value();
+ QColor newColor = QColor(color.name());
+ newColor.setAlpha(color.alpha());
+ castedValue = QVariant(newColor);
+ }
+
+ if (!value->value().isValid() || (propertyTypeUrl && value->value().toString().isEmpty())) { // reset
+ removePropertyFromModel(propertyName);
+ } else {
+ // QVector*D(0, 0, 0) detects as null variant though it is valid value
+ if (castedValue.isValid()
+ && (!castedValue.isNull() || castedValue.type() == QVariant::Vector2D
+ || castedValue.type() == QVariant::Vector3D
+ || castedValue.type() == QVariant::Vector4D)) {
+ commitVariantValueToModel(propertyName, castedValue);
+ }
+ }
+}
+
+static bool isTrueFalseLiteral(const QString &expression)
+{
+ return (expression.compare("false", Qt::CaseInsensitive) == 0)
+ || (expression.compare("true", Qt::CaseInsensitive) == 0);
+}
+
+void TextureEditorView::changeExpression(const QString &propertyName)
+{
+ PropertyName name = propertyName.toUtf8();
+
+ if (name.isNull() || locked() || noValidSelection())
+ return;
+
+ executeInTransaction("TextureEditorView::changeExpression", [this, name] {
+ PropertyName underscoreName(name);
+ underscoreName.replace('.', '_');
+
+ QmlObjectNode qmlObjectNode(m_selectedTexture);
+ PropertyEditorValue *value = m_qmlBackEnd->propertyValueForName(QString::fromLatin1(underscoreName));
+
+ if (!value) {
+ qWarning() << __FUNCTION__ << "no value for " << underscoreName;
+ return;
+ }
+
+ if (auto property = m_selectedTexture.metaInfo().property(name)) {
+ auto propertyTypeName = property.propertyType().typeName();
+ if (propertyTypeName == "QColor") {
+ if (QColor(value->expression().remove('"')).isValid()) {
+ qmlObjectNode.setVariantProperty(name, QColor(value->expression().remove('"')));
+ return;
+ }
+ } else if (propertyTypeName == "bool") {
+ if (isTrueFalseLiteral(value->expression())) {
+ if (value->expression().compare("true", Qt::CaseInsensitive) == 0)
+ qmlObjectNode.setVariantProperty(name, true);
+ else
+ qmlObjectNode.setVariantProperty(name, false);
+ return;
+ }
+ } else if (propertyTypeName == "int") {
+ bool ok;
+ int intValue = value->expression().toInt(&ok);
+ if (ok) {
+ qmlObjectNode.setVariantProperty(name, intValue);
+ return;
+ }
+ } else if (propertyTypeName == "qreal") {
+ bool ok;
+ qreal realValue = value->expression().toDouble(&ok);
+ if (ok) {
+ qmlObjectNode.setVariantProperty(name, realValue);
+ return;
+ }
+ } else if (propertyTypeName == "QVariant") {
+ bool ok;
+ qreal realValue = value->expression().toDouble(&ok);
+ if (ok) {
+ qmlObjectNode.setVariantProperty(name, realValue);
+ return;
+ } else if (isTrueFalseLiteral(value->expression())) {
+ if (value->expression().compare("true", Qt::CaseInsensitive) == 0)
+ qmlObjectNode.setVariantProperty(name, true);
+ else
+ qmlObjectNode.setVariantProperty(name, false);
+ return;
+ }
+ }
+ }
+
+ if (value->expression().isEmpty()) {
+ value->resetValue();
+ return;
+ }
+
+ if (qmlObjectNode.expression(name) != value->expression() || !qmlObjectNode.propertyAffectedByCurrentState(name))
+ qmlObjectNode.setBindingProperty(name, value->expression());
+ });
+}
+
+void TextureEditorView::exportPropertyAsAlias(const QString &name)
+{
+ if (name.isNull() || locked() || noValidSelection())
+ return;
+
+ executeInTransaction("TextureEditorView::exportPopertyAsAlias", [this, name] {
+ const QString id = m_selectedTexture.validId();
+ QString upperCasePropertyName = name;
+ upperCasePropertyName.replace(0, 1, upperCasePropertyName.at(0).toUpper());
+ QString aliasName = id + upperCasePropertyName;
+ aliasName.replace(".", ""); //remove all dots
+
+ PropertyName propertyName = aliasName.toUtf8();
+ if (rootModelNode().hasProperty(propertyName)) {
+ Core::AsynchronousMessageBox::warning(tr("Cannot Export Property as Alias"),
+ tr("Property %1 does already exist for root component.").arg(aliasName));
+ return;
+ }
+ rootModelNode().bindingProperty(propertyName).setDynamicTypeNameAndExpression("alias", id + "." + name);
+ });
+}
+
+void TextureEditorView::removeAliasExport(const QString &name)
+{
+ if (name.isNull() || locked() || noValidSelection())
+ return;
+
+ executeInTransaction("TextureEditorView::removeAliasExport", [this, name] {
+ const QString id = m_selectedTexture.validId();
+
+ const QList bindingProps = rootModelNode().bindingProperties();
+ for (const BindingProperty &property : bindingProps) {
+ if (property.expression() == (id + "." + name)) {
+ rootModelNode().removeProperty(property.name());
+ break;
+ }
+ }
+ });
+}
+
+bool TextureEditorView::locked() const
+{
+ return m_locked;
+}
+
+void TextureEditorView::currentTimelineChanged(const ModelNode &)
+{
+ m_qmlBackEnd->contextObject()->setHasActiveTimeline(QmlTimeline::hasActiveTimeline(this));
+}
+
+Internal::DynamicPropertiesModel *TextureEditorView::dynamicPropertiesModel() const
+{
+ return m_dynamicPropertiesModel;
+}
+
+TextureEditorView *TextureEditorView::instance()
+{
+ static TextureEditorView *s_instance = nullptr;
+
+ if (s_instance)
+ return s_instance;
+
+ const auto views = QmlDesignerPlugin::instance()->viewManager().views();
+ for (auto *view : views) {
+ TextureEditorView *myView = qobject_cast(view);
+ if (myView)
+ s_instance = myView;
+ }
+
+ QTC_ASSERT(s_instance, return nullptr);
+ return s_instance;
+}
+
+void TextureEditorView::timerEvent(QTimerEvent *timerEvent)
+{
+ if (m_timerId == timerEvent->timerId())
+ resetView();
+}
+
+void TextureEditorView::resetView()
+{
+ if (!model())
+ return;
+
+ m_locked = true;
+
+ if (m_timerId)
+ killTimer(m_timerId);
+
+ setupQmlBackend();
+
+ if (m_qmlBackEnd)
+ m_qmlBackEnd->emitSelectionChanged();
+
+ m_locked = false;
+
+ if (m_timerId)
+ m_timerId = 0;
+}
+
+// static
+QString TextureEditorView::textureEditorResourcesPath()
+{
+#ifdef SHARE_QML_PATH
+ if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
+ return QLatin1String(SHARE_QML_PATH) + "/textureEditorQmlSource";
+#endif
+ return Core::ICore::resourcePath("qmldesigner/textureEditorQmlSource").toString();
+}
+
+void TextureEditorView::applyTextureToSelectedModel(const ModelNode &texture)
+{
+ if (!m_selectedModel.isValid())
+ return;
+
+ QTC_ASSERT(texture.isValid(), return);
+
+ emitCustomNotification("apply_texture_to_model3D", {m_selectedModel, m_selectedTexture});
+}
+
+void TextureEditorView::handleToolBarAction(int action)
+{
+ QTC_ASSERT(m_hasQuick3DImport, return);
+
+ switch (action) {
+ case TextureEditorContextObject::ApplyToSelected: {
+ applyTextureToSelectedModel(m_selectedTexture);
+ break;
+ }
+
+ case TextureEditorContextObject::AddNewTexture: {
+ if (!model())
+ break;
+ executeInTransaction("TextureEditorView:handleToolBarAction", [&] {
+ ModelNode matLib = materialLibraryNode();
+ if (!matLib.isValid())
+ return;
+
+ NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.Texture");
+ ModelNode newTextureNode = createModelNode("QtQuick3D.Texture", metaInfo.majorVersion(),
+ metaInfo.minorVersion());
+ newTextureNode.validId();
+ matLib.defaultNodeListProperty().reparentHere(newTextureNode);
+ });
+ break;
+ }
+
+ case TextureEditorContextObject::DeleteCurrentTexture: {
+ if (m_selectedTexture.isValid())
+ m_selectedTexture.destroy();
+ break;
+ }
+
+ case TextureEditorContextObject::OpenMaterialBrowser: {
+ QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialBrowser", true);
+ break;
+ }
+ }
+}
+
+void TextureEditorView::setupQmlBackend()
+{
+ QUrl qmlPaneUrl;
+ QUrl qmlSpecificsUrl;
+ QString specificQmlData;
+
+ if (m_selectedTexture.isValid() && m_hasQuick3DImport && (materialLibraryNode().isValid() || m_hasTextureRoot)) {
+ qmlPaneUrl = QUrl::fromLocalFile(textureEditorResourcesPath() + "/TextureEditorPane.qml");
+
+ TypeName diffClassName;
+ if (NodeMetaInfo metaInfo = m_selectedTexture.metaInfo()) {
+ diffClassName = metaInfo.typeName();
+ for (const NodeMetaInfo &metaInfo : metaInfo.classHierarchy()) {
+ if (PropertyEditorQmlBackend::checkIfUrlExists(qmlSpecificsUrl))
+ break;
+ qmlSpecificsUrl = PropertyEditorQmlBackend::getQmlFileUrl(metaInfo.typeName()
+ + "Specifics", metaInfo);
+ diffClassName = metaInfo.typeName();
+ }
+
+ if (diffClassName != m_selectedTexture.type()) {
+ specificQmlData = PropertyEditorQmlBackend::templateGeneration(metaInfo,
+ model()->metaInfo(
+ diffClassName),
+ m_selectedTexture);
+ }
+ }
+ } else {
+ qmlPaneUrl = QUrl::fromLocalFile(textureEditorResourcesPath() + "/EmptyTextureEditorPane.qml");
+ }
+
+ TextureEditorQmlBackend *currentQmlBackend = m_qmlBackendHash.value(qmlPaneUrl.toString());
+
+ QString currentStateName = currentState().isBaseState() ? currentState().name() : "invalid state";
+
+ if (!currentQmlBackend) {
+ currentQmlBackend = new TextureEditorQmlBackend(this);
+
+ m_stackedWidget->addWidget(currentQmlBackend->widget());
+ m_qmlBackendHash.insert(qmlPaneUrl.toString(), currentQmlBackend);
+
+ currentQmlBackend->setup(m_selectedTexture, currentStateName, qmlSpecificsUrl, this);
+
+ currentQmlBackend->setSource(qmlPaneUrl);
+
+ QObject *rootObj = currentQmlBackend->widget()->rootObject();
+ QObject::connect(rootObj, SIGNAL(toolBarAction(int)), this, SLOT(handleToolBarAction(int)));
+ } else {
+ currentQmlBackend->setup(m_selectedTexture, currentStateName, qmlSpecificsUrl, this);
+ }
+
+ currentQmlBackend->widget()->installEventFilter(this);
+ currentQmlBackend->contextObject()->setHasQuick3DImport(m_hasQuick3DImport);
+ currentQmlBackend->contextObject()->setHasMaterialLibrary(materialLibraryNode().isValid());
+ currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData);
+
+ m_qmlBackEnd = currentQmlBackend;
+
+ if (m_hasTextureRoot)
+ m_dynamicPropertiesModel->setSelectedNode(m_selectedTexture);
+ else
+ m_dynamicPropertiesModel->reset();
+
+ m_stackedWidget->setCurrentWidget(m_qmlBackEnd->widget());
+}
+
+void TextureEditorView::commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value)
+{
+ m_locked = true;
+ executeInTransaction("TextureEditorView:commitVariantValueToModel", [&] {
+ QmlObjectNode(m_selectedTexture).setVariantProperty(propertyName, value);
+ });
+ m_locked = false;
+}
+
+void TextureEditorView::commitAuxValueToModel(const PropertyName &propertyName, const QVariant &value)
+{
+ m_locked = true;
+
+ PropertyName name = propertyName;
+ name.chop(5);
+
+ try {
+ if (value.isValid())
+ m_selectedTexture.setAuxiliaryData(AuxiliaryDataType::Document, name, value);
+ else
+ m_selectedTexture.removeAuxiliaryData(AuxiliaryDataType::Document, name);
+ }
+ catch (const Exception &e) {
+ e.showException();
+ }
+ m_locked = false;
+}
+
+void TextureEditorView::removePropertyFromModel(const PropertyName &propertyName)
+{
+ m_locked = true;
+ executeInTransaction("MaterialEditorView:removePropertyFromModel", [&] {
+ QmlObjectNode(m_selectedTexture).removeProperty(propertyName);
+ });
+ m_locked = false;
+}
+
+bool TextureEditorView::noValidSelection() const
+{
+ QTC_ASSERT(m_qmlBackEnd, return true);
+ return !QmlObjectNode::isValidQmlObjectNode(m_selectedTexture);
+}
+
+void TextureEditorView::modelAttached(Model *model)
+{
+ AbstractView::modelAttached(model);
+
+ m_locked = true;
+
+ m_hasQuick3DImport = model->hasImport("QtQuick3D");
+ m_hasTextureRoot = rootModelNode().metaInfo().isQtQuick3DTexture();
+
+ if (m_hasTextureRoot) {
+ m_selectedTexture = rootModelNode();
+ } else if (m_hasQuick3DImport) {
+ // Creating the material library node on model attach causes errors as long as the type
+ // information is not complete yet, so we keep checking until type info is complete.
+ m_ensureMatLibTimer.start(500);
+ }
+
+ if (!m_setupCompleted) {
+ reloadQml();
+ m_setupCompleted = true;
+ }
+ resetView();
+
+ m_locked = false;
+}
+
+void TextureEditorView::modelAboutToBeDetached(Model *model)
+{
+ AbstractView::modelAboutToBeDetached(model);
+ m_dynamicPropertiesModel->reset();
+ m_qmlBackEnd->textureEditorTransaction()->end();
+ m_qmlBackEnd->contextObject()->setHasMaterialLibrary(false);
+}
+
+void TextureEditorView::propertiesRemoved(const QList &propertyList)
+{
+ if (noValidSelection())
+ return;
+
+ for (const AbstractProperty &property : propertyList) {
+ ModelNode node(property.parentModelNode());
+
+ if (node.isRootNode())
+ m_qmlBackEnd->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedTexture).isAliasExported());
+
+ if (node == m_selectedTexture || QmlObjectNode(m_selectedTexture).propertyChangeForCurrentState() == node) {
+ setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).instanceValue(property.name()));
+ }
+
+ dynamicPropertiesModel()->dispatchPropertyChanges(property);
+ }
+}
+
+void TextureEditorView::variantPropertiesChanged(const QList &propertyList, PropertyChangeFlags /*propertyChange*/)
+{
+ if (noValidSelection())
+ return;
+
+ for (const VariantProperty &property : propertyList) {
+ ModelNode node(property.parentModelNode());
+ if (node == m_selectedTexture || QmlObjectNode(m_selectedTexture).propertyChangeForCurrentState() == node) {
+ if (property.isDynamic())
+ m_dynamicPropertiesModel->variantPropertyChanged(property);
+ if (m_selectedTexture.property(property.name()).isBindingProperty())
+ setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).instanceValue(property.name()));
+ else
+ setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).modelValue(property.name()));
+ }
+
+ dynamicPropertiesModel()->dispatchPropertyChanges(property);
+ }
+}
+
+void TextureEditorView::bindingPropertiesChanged(const QList &propertyList, PropertyChangeFlags /*propertyChange*/)
+{
+ if (noValidSelection())
+ return;
+
+ for (const BindingProperty &property : propertyList) {
+ ModelNode node(property.parentModelNode());
+
+ if (property.isAliasExport())
+ m_qmlBackEnd->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedTexture).isAliasExported());
+
+ if (node == m_selectedTexture || QmlObjectNode(m_selectedTexture).propertyChangeForCurrentState() == node) {
+ if (property.isDynamic())
+ m_dynamicPropertiesModel->bindingPropertyChanged(property);
+ if (QmlObjectNode(m_selectedTexture).modelNode().property(property.name()).isBindingProperty())
+ setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).instanceValue(property.name()));
+ else
+ setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).modelValue(property.name()));
+ }
+
+ dynamicPropertiesModel()->dispatchPropertyChanges(property);
+ }
+}
+
+void TextureEditorView::auxiliaryDataChanged(const ModelNode &node,
+ AuxiliaryDataKeyView key,
+ const QVariant &)
+{
+
+ if (noValidSelection() || !node.isSelected())
+ return;
+
+ m_qmlBackEnd->setValueforAuxiliaryProperties(m_selectedTexture, key);
+}
+
+void TextureEditorView::propertiesAboutToBeRemoved(const QList &propertyList)
+{
+ for (const auto &property : propertyList) {
+ if (property.isBindingProperty())
+ m_dynamicPropertiesModel->bindingRemoved(property.toBindingProperty());
+ else if (property.isVariantProperty())
+ m_dynamicPropertiesModel->variantRemoved(property.toVariantProperty());
+ }
+}
+
+void TextureEditorView::nodeReparented(const ModelNode &node,
+ const NodeAbstractProperty &newPropertyParent,
+ const NodeAbstractProperty &oldPropertyParent,
+ PropertyChangeFlags propertyChange)
+{
+ if (node.id() == Constants::MATERIAL_LIB_ID && m_qmlBackEnd && m_qmlBackEnd->contextObject())
+ m_qmlBackEnd->contextObject()->setHasMaterialLibrary(true);
+}
+
+void TextureEditorView::nodeAboutToBeRemoved(const ModelNode &removedNode)
+{
+ if (removedNode.id() == Constants::MATERIAL_LIB_ID && m_qmlBackEnd && m_qmlBackEnd->contextObject())
+ m_qmlBackEnd->contextObject()->setHasMaterialLibrary(false);
+}
+
+bool TextureEditorView::hasWidget() const
+{
+ return true;
+}
+
+WidgetInfo TextureEditorView::widgetInfo()
+{
+ return createWidgetInfo(m_stackedWidget,
+ "TextureEditor",
+ WidgetInfo::RightPane,
+ 0,
+ tr("Texture Editor"));
+}
+
+void TextureEditorView::selectedNodesChanged(const QList &selectedNodeList,
+ [[maybe_unused]] const QList &lastSelectedNodeList)
+{
+ m_selectedModel = {};
+
+ if (selectedNodeList.size() == 1 && selectedNodeList.at(0).metaInfo().isQtQuick3DModel())
+ m_selectedModel = selectedNodeList.at(0);
+
+ m_qmlBackEnd->contextObject()->setHasModelSelection(m_selectedModel.isValid());
+}
+
+void TextureEditorView::currentStateChanged(const ModelNode &node)
+{
+ QmlModelState newQmlModelState(node);
+ Q_ASSERT(newQmlModelState.isValid());
+ resetView();
+}
+
+void TextureEditorView::instancePropertyChanged(const QList> &propertyList)
+{
+ if (!m_selectedTexture.isValid() || !m_qmlBackEnd)
+ return;
+
+ m_locked = true;
+
+ for (const QPair &propertyPair : propertyList) {
+ const ModelNode modelNode = propertyPair.first;
+ const QmlObjectNode qmlObjectNode(modelNode);
+ const PropertyName propertyName = propertyPair.second;
+
+ if (qmlObjectNode.isValid() && modelNode == m_selectedTexture && qmlObjectNode.currentState().isValid()) {
+ const AbstractProperty property = modelNode.property(propertyName);
+ if (!modelNode.hasProperty(propertyName) || modelNode.property(property.name()).isBindingProperty())
+ setValue(modelNode, property.name(), qmlObjectNode.instanceValue(property.name()));
+ else
+ setValue(modelNode, property.name(), qmlObjectNode.modelValue(property.name()));
+ }
+ }
+
+ m_locked = false;
+}
+
+void TextureEditorView::importsChanged([[maybe_unused]] const QList &addedImports,
+ [[maybe_unused]] const QList &removedImports)
+{
+ m_hasQuick3DImport = model()->hasImport("QtQuick3D");
+ m_qmlBackEnd->contextObject()->setHasQuick3DImport(m_hasQuick3DImport);
+
+ if (m_hasQuick3DImport)
+ m_ensureMatLibTimer.start(500);
+
+ resetView();
+}
+
+void TextureEditorView::duplicateTexture(const ModelNode &texture)
+{
+ QTC_ASSERT(texture.isValid(), return);
+
+ if (!model())
+ return;
+
+ TypeName matType = texture.type();
+ QmlObjectNode sourceTexture(texture);
+ ModelNode duplicateTextureNode;
+ QList dynamicProps;
+
+ executeInTransaction(__FUNCTION__, [&] {
+ ModelNode matLib = materialLibraryNode();
+ if (!matLib.isValid())
+ return;
+
+ // create the duplicate texture
+ NodeMetaInfo metaInfo = model()->metaInfo(matType);
+ QmlObjectNode duplicateTex = createModelNode(matType, metaInfo.majorVersion(), metaInfo.minorVersion());
+
+ duplicateTextureNode = duplicateTex .modelNode();
+ duplicateTextureNode.validId();
+
+ // sync properties. Only the base state is duplicated.
+ const QList props = texture.properties();
+ for (const AbstractProperty &prop : props) {
+ if (prop.name() == "objectName" || prop.name() == "data")
+ continue;
+
+ if (prop.isVariantProperty()) {
+ if (prop.isDynamic()) {
+ dynamicProps.append(prop);
+ } else {
+ duplicateTextureNode.variantProperty(prop.name())
+ .setValue(prop.toVariantProperty().value());
+ }
+ } else if (prop.isBindingProperty()) {
+ if (prop.isDynamic()) {
+ dynamicProps.append(prop);
+ } else {
+ duplicateTextureNode.bindingProperty(prop.name())
+ .setExpression(prop.toBindingProperty().expression());
+ }
+ }
+ }
+
+ matLib.defaultNodeListProperty().reparentHere(duplicateTex);
+ });
+
+ // For some reason, creating dynamic properties in the same transaction doesn't work, so
+ // let's do it in separate transaction.
+ // TODO: Fix the issue and merge transactions (QDS-8094)
+ if (!dynamicProps.isEmpty()) {
+ executeInTransaction(__FUNCTION__, [&] {
+ for (const AbstractProperty &prop : std::as_const(dynamicProps)) {
+ if (prop.isVariantProperty()) {
+ duplicateTextureNode.variantProperty(prop.name())
+ .setDynamicTypeNameAndValue(prop.dynamicTypeName(),
+ prop.toVariantProperty().value());
+ } else if (prop.isBindingProperty()) {
+ duplicateTextureNode.bindingProperty(prop.name())
+ .setDynamicTypeNameAndExpression(prop.dynamicTypeName(),
+ prop.toBindingProperty().expression());
+ }
+ }
+ });
+ }
+}
+
+void TextureEditorView::customNotification([[maybe_unused]] const AbstractView *view,
+ const QString &identifier,
+ const QList &nodeList,
+ const QList &data)
+{
+ if (identifier == "selected_texture_changed") {
+ if (!m_hasTextureRoot) {
+ m_selectedTexture = nodeList.first();
+ m_dynamicPropertiesModel->setSelectedNode(m_selectedTexture);
+ QTimer::singleShot(0, this, &TextureEditorView::resetView);
+ }
+ } else if (identifier == "apply_texture_to_selected_triggered") {
+ applyTextureToSelectedModel(nodeList.first());
+ } else if (identifier == "add_new_texture") {
+ handleToolBarAction(TextureEditorContextObject::AddNewTexture);
+ } else if (identifier == "duplicate_texture") {
+ duplicateTexture(nodeList.first());
+ }
+}
+
+void QmlDesigner::TextureEditorView::highlightSupportedProperties(bool highlight)
+{
+ if (!m_selectedTexture.isValid())
+ return;
+
+ DesignerPropertyMap &propMap = m_qmlBackEnd->backendValuesPropertyMap();
+ const QStringList propNames = propMap.keys();
+ NodeMetaInfo metaInfo = m_selectedTexture.metaInfo();
+ QTC_ASSERT(metaInfo.isValid(), return);
+
+ for (const QString &propName : propNames) {
+ if (metaInfo.property(propName.toUtf8()).propertyType().isQtQuick3DTexture()) { // TODO: support dropping to texture source
+ QObject *propEditorValObj = propMap.value(propName).value();
+ PropertyEditorValue *propEditorVal = qobject_cast(propEditorValObj);
+ propEditorVal->setHasActiveDrag(highlight);
+ }
+ }
+}
+
+void TextureEditorView::dragStarted(QMimeData *mimeData)
+{
+ if (!mimeData->hasFormat(Constants::MIME_TYPE_ASSETS))
+ return;
+
+ const QString assetPath = QString::fromUtf8(mimeData->data(Constants::MIME_TYPE_ASSETS)).split(',')[0];
+ QString assetType = AssetsLibraryWidget::getAssetTypeAndData(assetPath).first;
+
+ if (assetType != Constants::MIME_TYPE_ASSET_IMAGE) // currently only image assets have dnd-supported properties
+ return;
+
+ highlightSupportedProperties();
+}
+
+void TextureEditorView::dragEnded()
+{
+ highlightSupportedProperties(false);
+}
+
+// from model to texture editor
+void TextureEditorView::setValue(const QmlObjectNode &qmlObjectNode, const PropertyName &name, const QVariant &value)
+{
+ m_locked = true;
+ m_qmlBackEnd->setValue(qmlObjectNode, name, value);
+ m_locked = false;
+}
+
+bool TextureEditorView::eventFilter(QObject *obj, QEvent *event)
+{
+ if (event->type() == QEvent::FocusOut) {
+ if (m_qmlBackEnd && m_qmlBackEnd->widget() == obj)
+ QMetaObject::invokeMethod(m_qmlBackEnd->widget()->rootObject(), "closeContextMenu");
+ }
+ return QObject::eventFilter(obj, event);
+}
+
+void TextureEditorView::reloadQml()
+{
+ m_qmlBackendHash.clear();
+ while (QWidget *widget = m_stackedWidget->widget(0)) {
+ m_stackedWidget->removeWidget(widget);
+ delete widget;
+ }
+ m_qmlBackEnd = nullptr;
+
+ resetView();
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h
new file mode 100644
index 00000000000..8e5c175e853
--- /dev/null
+++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.h
@@ -0,0 +1,127 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include
+#include
+
+#include
+#include
+#include
+
+QT_BEGIN_NAMESPACE
+class QShortcut;
+class QStackedWidget;
+class QTimer;
+class QColorDialog;
+QT_END_NAMESPACE
+
+namespace QmlDesigner {
+
+class ModelNode;
+class TextureEditorQmlBackend;
+
+namespace Internal {
+class DynamicPropertiesModel;
+}
+
+class TextureEditorView : public AbstractView
+{
+ Q_OBJECT
+
+public:
+ TextureEditorView(ExternalDependenciesInterface &externalDependencies);
+ ~TextureEditorView() override;
+
+ bool hasWidget() const override;
+ WidgetInfo widgetInfo() override;
+
+ void selectedNodesChanged(const QList &selectedNodeList,
+ const QList &lastSelectedNodeList) override;
+
+ void propertiesRemoved(const QList &propertyList) override;
+
+ void modelAttached(Model *model) override;
+ void modelAboutToBeDetached(Model *model) override;
+
+ void variantPropertiesChanged(const QList &propertyList, PropertyChangeFlags propertyChange) override;
+ void bindingPropertiesChanged(const QList &propertyList, PropertyChangeFlags propertyChange) override;
+ void auxiliaryDataChanged(const ModelNode &node,
+ AuxiliaryDataKeyView key,
+ const QVariant &data) override;
+ void propertiesAboutToBeRemoved(const QList &propertyList) override;
+ void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent,
+ const NodeAbstractProperty &oldPropertyParent,
+ AbstractView::PropertyChangeFlags propertyChange) override;
+ void nodeAboutToBeRemoved(const ModelNode &removedNode) override;
+
+ void resetView();
+ void currentStateChanged(const ModelNode &node) override;
+ void instancePropertyChanged(const QList > &propertyList) override;
+
+ void importsChanged(const QList &addedImports, const QList &removedImports) override;
+ void customNotification(const AbstractView *view, const QString &identifier,
+ const QList &nodeList, const QList &data) override;
+
+ void dragStarted(QMimeData *mimeData) override;
+ void dragEnded() override;
+
+ void changeValue(const QString &name);
+ void changeExpression(const QString &name);
+ void exportPropertyAsAlias(const QString &name);
+ void removeAliasExport(const QString &name);
+
+ bool locked() const;
+
+ void currentTimelineChanged(const ModelNode &node) override;
+
+ Internal::DynamicPropertiesModel *dynamicPropertiesModel() const;
+
+ static TextureEditorView *instance();
+
+public slots:
+ void handleToolBarAction(int action);
+
+protected:
+ void timerEvent(QTimerEvent *event) override;
+ void setValue(const QmlObjectNode &fxObjectNode, const PropertyName &name, const QVariant &value);
+ bool eventFilter(QObject *obj, QEvent *event) override;
+
+private:
+ static QString textureEditorResourcesPath();
+
+ void reloadQml();
+ void highlightSupportedProperties(bool highlight = true);
+
+ void applyTextureToSelectedModel(const ModelNode &texture);
+
+ void setupQmlBackend();
+
+ void commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value);
+ void commitAuxValueToModel(const PropertyName &propertyName, const QVariant &value);
+ void removePropertyFromModel(const PropertyName &propertyName);
+ void duplicateTexture(const ModelNode &texture);
+
+ bool noValidSelection() const;
+
+ ModelNode m_selectedTexture;
+ QTimer m_ensureMatLibTimer;
+ QShortcut *m_updateShortcut = nullptr;
+ int m_timerId = 0;
+ QStackedWidget *m_stackedWidget = nullptr;
+ ModelNode m_selectedModel;
+ QHash m_qmlBackendHash;
+ TextureEditorQmlBackend *m_qmlBackEnd = nullptr;
+ bool m_locked = false;
+ bool m_setupCompleted = false;
+ bool m_hasQuick3DImport = false;
+ bool m_hasTextureRoot = false;
+ bool m_initializingPreviewData = false;
+
+ QPointer m_colorDialog;
+ QPointer m_itemLibraryInfo;
+ Internal::DynamicPropertiesModel *m_dynamicPropertiesModel = nullptr;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h
index 61cc2cf316b..b178bf1837b 100644
--- a/src/plugins/qmldesigner/qmldesignerconstants.h
+++ b/src/plugins/qmldesigner/qmldesignerconstants.h
@@ -113,6 +113,7 @@ const char EVENT_TRANSITIONEDITOR_TIME[] = "transitionEditor";
const char EVENT_CURVEDITOR_TIME[] = "curveEditor";
const char EVENT_STATESEDITOR_TIME[] = "statesEditor";
const char EVENT_TEXTEDITOR_TIME[] = "textEditor";
+const char EVENT_TEXTUREEDITOR_TIME[] = "textureEditor";
const char EVENT_PROPERTYEDITOR_TIME[] = "propertyEditor";
const char EVENT_ASSETSLIBRARY_TIME[] = "assetsLibrary";
const char EVENT_ITEMLIBRARY_TIME[] = "itemLibrary";