From 815dd39e478378b01c3489d9ecd2e85dedb75b48 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Thu, 17 Nov 2022 12:54:19 +0200 Subject: [PATCH] QmlDesigner: Implement Texture Editor view Fixes: QDS-8209 Change-Id: Ief0c9f56da79841c745595024dbcd9219072b681 Reviewed-by: Miikka Heikkinen --- .../materialBrowserQmlSource/TextureItem.qml | 1 + .../MaterialEditorTopSection.qml | 1 + .../EmptyTextureEditorPane.qml | 50 + .../TextureEditorPane.qml | 65 ++ .../TextureEditorToolBar.qml | 69 ++ .../TextureEditorTopSection.qml | 45 + src/plugins/qmldesigner/CMakeLists.txt | 12 + .../components/componentcore/viewmanager.cpp | 5 + .../components/edit3d/edit3dview.cpp | 99 +- .../components/edit3d/edit3dview.h | 1 + .../materialbrowsertexturesmodel.cpp | 14 + .../materialbrowsertexturesmodel.h | 7 + .../materialbrowser/materialbrowserview.cpp | 24 +- .../materialbrowser/materialbrowserwidget.cpp | 2 +- .../materialeditor/images/texture_default.png | Bin 3219 -> 0 bytes .../images/texture_default@2x.png | Bin 7659 -> 0 bytes .../materialeditor/materialeditor.qrc | 2 - .../materialeditor/materialeditorview.cpp | 6 + .../textureeditor/images/texture_default.png | Bin 0 -> 5883 bytes .../images/texture_default@2x.png | Bin 0 -> 15348 bytes .../textureeditor/textureeditor.qrc | 6 + .../textureeditorcontextobject.cpp | 324 +++++++ .../textureeditorcontextobject.h | 156 ++++ ...xtureeditordynamicpropertiesproxymodel.cpp | 22 + ...textureeditordynamicpropertiesproxymodel.h | 16 + .../textureeditor/textureeditorqmlbackend.cpp | 310 +++++++ .../textureeditor/textureeditorqmlbackend.h | 69 ++ .../textureeditortransaction.cpp | 48 + .../textureeditor/textureeditortransaction.h | 31 + .../textureeditor/textureeditorview.cpp | 866 ++++++++++++++++++ .../textureeditor/textureeditorview.h | 127 +++ .../qmldesigner/qmldesignerconstants.h | 1 + 32 files changed, 2324 insertions(+), 55 deletions(-) create mode 100644 share/qtcreator/qmldesigner/textureEditorQmlSource/EmptyTextureEditorPane.qml create mode 100644 share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorPane.qml create mode 100644 share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorToolBar.qml create mode 100644 share/qtcreator/qmldesigner/textureEditorQmlSource/TextureEditorTopSection.qml delete mode 100644 src/plugins/qmldesigner/components/materialeditor/images/texture_default.png delete mode 100644 src/plugins/qmldesigner/components/materialeditor/images/texture_default@2x.png create mode 100644 src/plugins/qmldesigner/components/textureeditor/images/texture_default.png create mode 100644 src/plugins/qmldesigner/components/textureeditor/images/texture_default@2x.png create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditor.qrc create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.h create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.cpp create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditordynamicpropertiesproxymodel.h create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.cpp create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditortransaction.h create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp create mode 100644 src/plugins/qmldesigner/components/textureeditor/textureeditorview.h 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 70e85cc2ebae18303e61189afb839ab646e11207..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3219 zcmeAS@N?(olHy`uVBq!ia0y~yV2A=?4rT@h2L7^*1`G_0{Q*89t_%zejEs!SmMvSp zd^v<%x^yXoft5rPMB`!_f@T^r7nj~;FaS3VCIE6IeiM*ok+tJuFI%<@O$!g&z?Ph{^G^USFc~ce)Hz-+qduDy?_7V!^e-GK7IcDbT`I*}o7`O~QT^vIqT5}_Non_ob@9(s}vpYe!MZ-5l>CRLWrI3}O zi!^$-n)oVzTr3jQc(^OqCn#4-MdF<8j+yp%D(~m(-`#!t_T8D4pPzlNd!Jn1eIdi_ z1-JZ-l==208fTNGV-CpOxRhhIRbEcawYoO_`Q*8C@5=ssdx=s0)v@b$TqT2EtL-hz zYhQGy+wdrd^`FPa`=piA_Oz%E$KWF6Qw&P{}{zUjy!}G?zUCzr(eIiY|I4;dRS0XjL+j@mZCbyAViFQMs zxNCpVH7kp$+|x_~w@JAwJ~v8N+{6{ytXI_kPQYisb_0*tlI0EsKiS{s%FD^VyYn`d zb5BrX!%{92t1i(Op>hHTtxlX$54X48t6;=C?_ZhMwCpVHxH}87m=zoxE?V(EYi3r` zk>%Sv-zGA5#?|0$8-Jb59)H-R+}qvb@xq|sT$VJJ+_KrO)$-H+ z6*+c_Oq>6F+TlF!2i3LK^TS^o?Fs0!=8@gWG)s^nvs*It&YQ_o&i$_lJu&OQRe90# z&8p8$9WC^PJA55EKJVg8^SStO*T+D&^6FI@GA&Qpcb+~RG}q|Ftw|1E zP9|L4{4=iS|6J`o5vo1iGnbUII!@hd*t<#c<*(Q0joWAE9o+Nk>KpUzI%OFi8x=%T zR_^sGdMvl&%uI`4HF0Ni&qs%SIjc4A|D8uItlIvChnA^-{1xr8kCpBFvB;j+hr&}s z0`Ht!bl))lzQil>L0yE%CmWRpG|kxFcDeSA*kLFX~h4FkODP_MFj@ z3*|*ARywVJxsqQmmhgXWD&#rgvfaG}vBw3Tneye-Nt`>xg?RUU*Ew2$zW3vUQ}>gt zV#^fuI{c#|0@;_{(a@W0(%5?Ju1)ZN(^$EZ`G1_lQnn}mTbV7CzqR2+-#J5-q9=>} zF8@FCTt0s55@EIXsTO^^O}~enFI+7#c5}{@(^wJo^4>Fvp#I5r4@8o=jm($S{(K!j_0`kH z)1C#A%eU%XJ8r}7exYEY&N=s|#|n1DSurPXRG7$rc(POCN5?SD<~y584}EistKS+I zbt%wz+eW^NN)f#J_ZCiU*3^H>`G3>7h*M2D$Ar(GShSLFqw3Y>y+^Bdukc8pdvd$h zk(P~e+Gm^=I&>}Eb5-Pzl+n}O5#H+4)|Pfy?CL68vvlLqP3L`|Ma1QWDDN#@Q5P)r zGsaE+4EIZw(gR+|1DooQcYY z<7-$L_r0Fa1+ORV@(+mqcjFAlX2ok#6`n;_yJ~yxG1zQeaAZ>ASAM;dK^LFfD@4mV z1o+P3nsn#%g=@84nIB(7CI6II+iVl)z|q_D`I_o#yMklB-+Nw4=l|dBzv1x3uQCs( z_h0P&o;Jn)W>b&bwVSV4WbWSU`SXM`f648laD{hsFE*r2k#Q(5n_T@PCZq>77*`R8hdZj4nl3t`h$u^B$HA1W@Q+OwDa%Bz;G`fl`z;qQy6;GVb2%{`!*)0|NhFo;_LQ3{ADjKc z%JM)~)A4L=^%X2X_gJh>wR7v2%jW!9;OuY6kTY|Z5{vLT>xFD{d|b{uzv0vIS`(Zy zSMlz#eUl$=zj*lOq(z?s;^qll6MCI8_n5A(;>QDzw*TC&Gcnvc%+JXDO1&t*r-D;g zlB>bWnYSW0{o^paUgOUv@cy>2iTy+4fI{S6aHypV#i^PD zOJB|1xl{VbLf`(p8{2c-K3Bud(FKlRs9UTJiGCJxee5eTADAL{-mR`FE1( zO_8nNr>*yxcD8-_lj7%)Znud(qV_Umu?f_1xvcy8n`by|;7T lO^CiYvlj{ 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 a27efb275d39b149b901835c57ad93c750a686e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7659 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 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 0000000000000000000000000000000000000000..32ad4e3eda62887f6faea7a26c394b03493cd519 GIT binary patch literal 5883 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 OFnGH9xvXK2O}ofn*_6uKs2fxu0d zMW&pJaSsE6W(Mt4jGQ*D)5ZJioMuXZbTHx~LGm=b-hC+*#x>UWm!E6@M7d;i_y{odlY+kc&Z@c&uh=N*+=e-7<@ zX8z&C_Iz&pZSS-D1?TY}&x{w?w}-#}aPh~j9}ZQ>*~#BKzWAB)w)vdvKYY}9nfp-b z%XzyvrysNJPgUEjTW-GG{Nc(a+CA_4#O3VyqJ;bp1<&4bWtN?()f{QFIIFsx#W$kt zVyAxo$=CLq>wM*|nq9UpS6-P`bLrvY1-*Oxx68~jwK>H*&EMeO{*pMo?a?_GOG+y| zZ}eR#PwMwQ`&2eoJT7WiyWRdh3%E* z*4F0U@x?#)@BH;XrsfOpuiD>_W&Rkn=S$lJ|3Ccwy8kNwnc4NW%l#DhsmV@@ThM3Z ze*QeKy}&-Zn8?~Y`4^&}zxZ-!`wjDF|I0p~nyNkf?EOFY(?0*7s{McO-}hb>6{*H{ zX8mTDm8&{`-f9tV_rH0kC4Nc!>{p%IKY4%tem3=w{{LC_xy8lBll_G5IV`{UZ-SlS z{HyizIBm1*yjQJS^?&Ps&)>;E{Oft=_35vVx2~8Yf2{mT`Lg?*GDfvBHBNO4Jyd@Q zYyWZiDpQU2V1ZgtMi zRtDypckh{q{64kn@D0W017+3{Kdv2&+2Qqfmw%hXg?UceAwhHA|5{qz9+n%S{NRJo z?fm5Z8UMVE8&+5<>*NdNP^i$eySYDEwyMX%;pSy|uA={8c7g*GN`D?~I51(YH7i{F47;@^6=eEsDZ);bI1x^ku>pT5zM%tyWy>m{^xQx zyHa<3jq;jp*~_>5yi=FCaYF9OjOB{FMw(IGd#r@MeQ>QlA-dZx#izn=scZLwQr(~B zNj=M?Hb1fD{>r`kPGpl~OcZ?GUs1 z=IQlqy+^&ihCV&*`EBzJ&9zq?M059iYc!HI?8B>1F$?{zEW30-ROPnu?c_3 zX{lq{^z=o{9+fjZ6K5*6t~2VeI@4Nl?NC_ZOzou$0_3CiZP_JkFzt;~mc8XVj+XpP z)1q?|9vM8C=|81^X>My*@PfJGc`2QN?HeY<-8eOO`Ia4*(w9Awd{gYPR9$PIjAR;b zuDql6YNvh)S%X?OHWxczNBN$%QpKi4O8%wEXIoGFITV(twrBV5>hcd;cSrxD}FPqZHqU%#q+{io%%*89Z!HqVON81-Yq)v3?lhiO^a6nH&9bj!eEY172(Z-e_j zdChqIX+ltsrThBN8|EDObT84!RQ;~S+3Ea`44q$T9)5o1qO7WtW%7$TUXQFiTCDhA zCjGd3>i@>a8^3ODY7~DOe?P8GL_5W%c$TPKvXG$h>WkW&SX0INyd543~3kl z`016^GbQ=^gf%54hZ`riCMov(s?ZMktx7%^&c0MS%JMYqhmsU0@28I*o z2j7@+x@^XQmMxa4vlVapl^;pG6~Fh+^TkgMA6(}6u!Q}<6w7BzU+1qbub8)Hc51Pi z%+@#$=ZsB1>#HS?@U5CXxvt^!u0Kg06RpqfoxDOxb>8JvOOM#+B9>bEmrARI)SrYs z{Ks{(WQ#%-&$jnbHy28H?Vd1?d;JXlb14t>m@ojC5Fg@Do%2HIbfFV;;>d8j8DxdG~bbwIsgy+8B}M zWW?hi##3UEP@1q|(La@Pld!mi<`n5yo@R`_tm{PtNY2y03k&o1NZxhRe;iE9*;(&*Et&S_YYi8@?&bUTANy%eJOM%0^|6 z)BZ!+;RinZe%`jRvM;|%EPbNX?5Dvzacdotk^}8O>8K=~75gdr@_O0}CuVKUImKBz zY2AKp7dEpdANJiR`ZMZY<@%{n^X&BY*_>3re$M9acsleZr3t%-i6f3vbc;ThBYiyuQS{fpeGtR^AYRIi!X*j=(R zSBzhhe_yY9zxP@HjOxN)F47BQ)ZNv1bM>N4rK0;(CmCeeMl0qYog$YWQ(N@a;LYla zw5WF`A9ebKd8@>@?_HApV!_;){dkJC#Qd4DH9scaWWN#l$%1>4gAu>kJhd%tR~!8% z^39Ir@Shz2uJ6TPt$Zc9il=?O?vvTqi{JWM)Xv6XS#D5!F!hj!w9J<)m(S<_IXnA( zUhMv#H`U+o|9h)HzT(+#Z@xMCuS%DE{o;3?x$oAohkH1eyLmg8NMD%hzWU|KB}>_| zbEh@DVR{sKu(|z2zO{7Kwi)-V-lQ}ou*KMX{`A|teE%=5!*Rc>cVEB%=ip-F3zKcU zW`7l7l=AHxZGOTDo9&Lc|HH=dD`~;erMVBzif(9 z_db961Jb!&HYVp7F}Qb{?6_H73N39f15=|gqGX= zxVSjW^x2g!PfqY1ih0JRl>6paR^a;DRW>hQY_**A^T|Q+5EH}Q&Yqudm0sXJbf?$g zo8#-g^m{kA9ui&pUE)vq-&WU>+_}M8xyR#HIlCXxd_UdSPR}%yhkZ|yf47$`U)0^k zH~u^B2L{iQ;?@n{5c%R)Rr_Vd6W1=sMpnL_7I^o?mAc<&{#~1Y_R-#}>1)@|w3gn* zv7^L^n;H#|9FJs)#qZG8T`_`&yY-oM+ktJQWp zbZYoqdB1nF*rlJmb+O$$8PV;w;QG%U3qk6lXVf^Sju6$`aI`t zk@JgGH-=wRn!h*x&+B_~>alH4zdj1Cp1$AvpzU$y%Z`y8Y)LN#f5(ZeYg#1l?kZyO zLUW(g?{5dr3+YC1nQ5mjiF|6@aj=>5V@gu4!|k2w_iuAwVqF<=OMT4`4(E-T$L?9V z9;(#)crK!Pz4*-s!i$3w!|f{BE}T;`i~h0Wmgd=)TfRA&HP6Vs5K+7M3O}1%%r+S- zgKUN8`|5M|M{osrojiQ~-RjRF|DW#Fx_0TcckslBY`AY1j;ABpVuE=RYg zw1z|qM_7H5eEhyS*emmG*w5!uYnxZJRBSdY5jha{+UUh|{(Uw6-VZM~$o$#8KVFE{ z_372>)e^dAh2>p;om2_XFuYwpgSqhPszp+9>u(wIKltPnwOS%UB1$ko`g~EGI3Wm%$DV} znp-jy@!c>;v}kw}`7Y9++quie&)w(FK~{d?ctH#t}2{*xuGx|~ONHt_AYG`*eZbiVij^VyibDbK2mZDC)o;k2SE z(FB8!^?XVj&OQ?8)!vb??#ajc>UIw)F*_(C;GOz3Z_V1v$-M#CZcAjky4T~$4*}SDq zrQ=hItp1jTdoBB&wlnNb-4v;EX>I#L{-ZOOEzy3)JNNN}rdgu(72T%_eqDWc-`>_c zFm2VEJpb1Zd(U515!hE}IRE(W=UW6-7lwW{m8$bOujG4Raj}1;ndwUP`Ag!YyL3yk z>@}}SySb*!ooE-TvC_?c{m%~L`P|yQ>+S7NpVYKE`DOOg@d>t}Cm&iCHC{ltG-z?wG`y4J1qxWVJf+`G)=lxtB(o3KZp&9Mtf{k1#J zFROiRy*aLxYtE*tZx^@U+i_g#V)b*`2bu z=akIsJ9xa*KkTAITlh~^?)|&`Uq9aK?Vq5|VBNFv!Z%T4{kw^UsrjwfwoTAFvcU96 z%brttD;#V(Ib{~BzR;Xi)jU6Sb)r+mP4=xtI;|5-q_y_6T)(+=vvr`s{gR1y&8Hdc zU4MjU!=4=d|2xlLywkbI)LB^fSTo0jhZz}`r#?(>ejxihSKw>VO3%JEzw+Nl?9A>t zDBf+HuP+_fa;oO{(eE0(JHLOLb1AVhy_aWA(!TQMLo2M$XB01$QBFS;_+*9iwf2KD zae{URa_g7GI{w^lyv1U3-$8D1786@BsVVV&k=_~VL9QtI(( zk$G)Zht2YDuv@FVBZ5EL_t%Rm0D@_0gZ~ki1W~SnT|;(-+70EizW~Zws7rBVy^Eh3#+n zLLY~$?Q&yWxgdMzf0k^go!>95`TT0jiBr|a+S}rn+dH5B@Np57T4s4iE%zI}DSitD z*p{ft9A)GJ6TC1N_&3fgejHXk|1CIEJ@CTQ8^;UlTfe_Q_E;w*c$>`JirvdT>hvF9 zBci@UbaJoo14p%$r%Z|uDTPD?=sx?VIj2U$El9rbqj1LkyU(X&G+gn2qaoG!Xo-aR2x$6#-224JM72aK)%8-|zcarsUfyjP ztnyy(701NKdvpl1s$&JXB3{`bh(v7{1)50sJEf(-mQ>I zV7#vQ;(=!RMKLSU$vR&@uYdErEBb40PvlJvZBQPk5?Bp@dYf_u0~d1&b8to&2`F=Y!;>gzni68NdGoifL@pce%PkwvjkxdA-Cx6h-#oc%|G~*(R|Q`dF;DVn{hYnGeS^~f4~~-@ttNf4 zI2ZPUL2u{#dn;3SN@a6}b*-zFX?t-rzpcllKsJcAsQ&j9vr7eBoAhj{jxjn3Q_s*tH%CbD?dqC>u%t!iDl&o}Q9-p#XYW)3PhJCguZ)Y@H_5rE& zMq7T_dF&3IW4|ZyTkMK`{|lx~GB0uT6MbfoW;*Yw)s>y^?M|AxXa~*-dNAqAwKcL^ zWsbj`FFSqqw)fn1VGDoV+nurb#GmUc4OBOu_+I%hIbz~|Bj&>NzTW7>eaB_0y=-&S zl?(EDV={YASnTErdlWPIvf1aR?RC>W{e5g;60lrEGxmnkyl7!{Kel_pi`M;Jv9$i& z$^O0KXJk^f|9|T=jHc$r(Lq|ubfc!=L&9MsS3Ni z!uVvVtcLP(%a90(twjZ16BBQ9+`jduk8kaf+uxt~yV;yR>*YjV3l7kptYgL7)%#bc=}$?Z;NQLHKCd#cnXX`} z|C`h2()DMd86sRQ7bhx(3iVj$i!QZFbn6U_{cuH1=hv(!Vkwj4QYPKJc`NSQDkIr- z?^Giu|6gCZG*M@2mY{h{Xv^2_y?@tC{$Db2OYppM-GD>&mtOstbm#0!C9~MqPqs%kjr z&sO!jP2r|bYm!B&iTX!{`)7yE`?%s6&r z^_e}qKF1t6>WucBI+OkTr`$cezyEFHTtzq{Z&ghDyv<_u&lU5XW<2^Q<@jFBLDMD><6d1g-dGhJ+KLJa?jkpZ!@p=4|Dkitr{~OW$%8w_heql(FT3Rj}|)* zo&1#WVDe-+-r$&b4iT2zJ0^9tHL5>snQqtGv?se;js0~flj~L??aX?nh~~ZWmu{Gg zFKt>DcXa;A_@ysh+!;H6El4R`T|S{uZ--}KvsQvbzSsjTdF^dC&q#0hvhVkl=UaLo zo}IGfTISQw52^!B)IAPV*;go^pey$N=QGb259VCzeZ=^)#KuYXkYjk+n`=%pT{kbC zxmieQ>AY>aEX@C!Z4dn4zivUbN~4icg=|t~)$Y4m3oIMIr(JPb)naV(Iq=g#YuQKT zZ6PN_Ijla({NRpxU2vdW)6Z{r2ej$YX-rvgOVTtDZ2=wG^T!-t%? z?G;<%XWp4Q=~eQ>?Ojvm?G?Tr)x6(YTV}_1ExVl6^`Z~l^UuDNlzVXYUE7@7JRjFs zSMF|Jp|@+&)cQ8Xduin%UzN=6cSjg){TqAq&tlWQTk(Re%bJfK*e#KEJE~xgQSdGI zvgD8-4aK<{*$Zp^r@lEWmMx;RG$@vFYZ2@3WR-Qy>qR39_tglqzg-%+{(w_f+_#>M zCl43?)yon1d{vwOhO5z=|0_2u#%x{uBYP=Vi5S~V2TzOr$4Uk6z1BRwd7V~5t-T0u zL_o0B+nMo-G0*OOp4Yj5o}_G;s?e;D3w`gbT)h2FsKtrQh1;DDX!g!YvZ{y|(!OcI z%W1G@xj?TA2V0?+v8G#8kL<#4SB&oMR^M(Y_V=Um6O|Pri;9;{jfl1U`$=xe>~Hb4 z2Q(kd`sf|%%J(Y3X0zqq-mXk3HbJq?{+p_fzdOE3()LHo#^zs_Hr%;refL@P*8}G} zyUHu$+>2Js5v;i7GoPL5$!Bve zb9V1}w`1=c(=8Jo8t46-FJQ6Z|0>O_qFW!o-Fj|!o0+4#=IbRMrmBd&3%Cze`lf{0 zvu%I+cY>43*6HgHZ!mgOH^b~t#-ufc!j^&>zrU_|sj9fPzO~G1$M;E}yi7IciH3fC zd|CeTvXJ>UZ?@WPNqF{Uj`M6g>s7@YGR)c5tyq4kFznrw&uu!BTh8hgbsT%WCHReH zf4us~&ufd_PA|4`3$^?IzJ7QAld09x##tASY`-fcRcaPBIbiB^p9J<<1|l_m)2(yM z8=jtf_#@IjY1;F%_Qf{av+o-CzwO_<*Zq55_Dh$~o8|5uik-y1@8vO`a~xIbtbFOU z>Dm2dJ8o7MK1=z$`CMLc{hv=?Rb3aAPt&hT=D6L8D|!7_-z|NA_QRFF>z{6% z-fO!z@z%G`#`O|^))?Emy05?5kUv+}V2UNn{hDZ-&y)M#&;IiL-s#Qt3V(FU^KO6I z{OfyF{{N#ZCBuH()O_3OU-nJgIpo&wvv1@%?6nuzFWHd#>f3Jd`Y+Az|DIgjZ?o&q zm!0DAmA~uXzngAWc)zxGq9C98wE0(+rq~IrJi~VUY;pYE`+py8o_%|F_3zL^@8<_6 z)D})lzdQN#`KPt4;F{_8VSyRDzIMDp6(>*voESspw2H2vsqoByx!3d2)wx!!wo z*5=3O`}5`m8a)?2eg0Hz^|Pb1iw?NW*l}I$&$oZ~?Ux1pR-ZoK_1M#yXa91AGcGDq zJG$1dxpB|WOFEKEh0mUURC?hN_xyL=D;a~F4c>mS`S9dk#Jtc=cZAQJ54zpOqoGv) zRr2Ycr5kuFYLcE``u@zTVDjWs=ZmVGo!-m;fBF8CnvPLT`s3a2YwE8}&3o_pG`%vI z=dwzC{QI}c*-{g9HLVJNH*bwpy>j`??#ai_KjI19x6Z!q-`dyJ8>Fos+8alWy${%Vo~|wX?kRXj@lV|Q>-GC=yi51`wQlfJvp;QJy|N(c zv$p=8vX_6e_rBe`_wVocy#MchU3(h8x8~c#$fM_#lsYcS&6la0cI*6E(VvmU{B~RZ zJh}P&+}y~@n#{zsWk=2%{g^ZP#QC7Ykn)JSu)^fI!pF{UTK(hjr_Eos`KV8r|Flc! z$l(v0r(FA}@@aF%A8`X(<&t7)w z{Houf!FjvCZ2h=7LdeF@{I*Ld(|JOuatea{Mh-dx1l|k7hSiC&|51OI{*cRq^&bE6^HN(vgRD$< z+TNJ-=F)!aw7O}JSU-C~2w~=WhUg5iL~Qpg zwLjPHTbb2~i6;K+{2A%zn>qR8=4<&I&dqsx@$2@}-p5a#e-v@|$RC~c29d%aH=p~? z#pRNydPO0rxaN<}T8WuG^N%`Zm`mDc?>*kcml#{P5M?# zwcz=0?(Z{bn*UU^{n3R#YtnibU%fQ-&EB0mu5AvD$gO!@@gtJ!O~|wKxjVPTPk$nR z=6ureL-Moci`HCZ*}~WVteH=cD#3F5T3(ciCDt{i<(n^OQHTtcz5av$XFvlS!7tPi`^ z{v>^Ag#5P9{ak-n{o8%5xvBD@&asQ1H0%(s-+))m~Q&l ztKyXOnr8ODTJxW8-rFV9X)z({{g;lPkx$lE_13OCt{^e-*eUTJk!{{(8gE(R^Z4meFtb)8~u!cn5vBz0-nm)%&H9 z>Z@KQ=Fc#W{TX>NzklwI`{#TMdH5ctul=fFGT*9h_Dy@Qx@k>sw|-8rZv5gpWlqed zyGez**EP$ZrknnB^nBA-!P;C`YGBVD)xEE2e`(7f9U-^&ZL3fm90h7{H@m(>rM63%gX8No6marfAHH|ksd|sxj$^zUonN{x_rsA z^ttatV@_X-i_E?4{%P~8YeF&n?Z4h$%ac{L*Pa_BJvsAdq{O?xZTr4G-MJ=S*k|{L z%}zX#%XhtN|N4NVF6@J!uYBg;wTqvmw_fwkbu|$cWIl82-h$qI1C1Lq=a>F-+Y;Ke z?Z-cT`8UVTS8cnlfA&UD^v}gl(^=z7_8&OEX}kX|kIlEj%Y5ptRp~El7L>n{|A^=A zkH~EE8`{0AZ`?X1uWNt$c;yCV`_-NK3nQf_&u_J3W~qF=?bVs{QM(sbvN-PldLi)h z=9q)xxqM}-&z#SC=CV~Xw7hOw*Y@Mi+g~3%_h-#kx%uHbdONq29Qm^*vF>+!y56*( zk=xBrt^BL^XU*5Ri+AqG|5%j#ar3pRR@R)ijyAzkvm;C;}e|D$Vh4tk3 zo+ZPzH%E88sQ9xc zy?Rm3e1pezxBje=+23Wh-jrG1R&d??DDEOI!{^3)pvvK57@y#Td&g3aJx^cz)rEUU zm)!X&KOzn7HL^M{^c?)QX4W;Py09+?9?iKicmCAZ?1?(xZr7Ds)Lo0>|6#MOciHLl zt8RN8>9Sx?HaMOqRyXZuqyeW#%+Hdi>88mB;;E`;#iy3aHO&`YQ)3cc%Xsj}l%J7r zB~0R+b8iP;3yA(1uyUSH`2I;U8($tdZ*~8%)4RH^?f!?(pHdDyQ!O!+e zHe1a4bI5gF+eDe^@6Vi<`m%c8DgI+IuU!7TdXHABjN^|__fezceF|FmT1$F=N& zVhKCg_FkAeKlFp^(yMhor_M+14lJAz)YmY-l&wfOWxb#HkH~|k!bNx|?eLR1d%i3C z`5w6mHf-uo(${_!F(~=Ro@<{QR%N%-Qoh*j1x$h2~KP16Yv}$%nLgW0c_n5!VY}wyD z=lXr!vd8I~PwqATStE0QmX!T#pEFBW?T+m~$oFTB0PmVdw^WZl)t~YsGQd3XQF!c+ z$XV-44D45jH6(kKC)I_$j$bL-X!3D&-k&uhnPJWQ&rUG3e_i9hcSGjme-78L_s&21 z_0Hm-k=eEzrB!6+%?~}_JoRhY(v5HL{)qf6?%QNvDRAceseKw>pQOpXIdncLG)cVf z+O7Q&-yec%X*m&@^KXC8n&A^(7q%rj=;&I(+kWzoKAm)rd>=Y>?YqefZpxiKf9c~S zy+3QV#)Y0N`4P!nt*YeqUhwbgb${0U>|P_m*JkZD>u@#OR)$^Ic3xd&d!u)L>cW`V zACcL%E0-NQpHy2dCtI4Aq*E8RAzDjhno-Kd{ga$l7GJs6_auF#@2fpbEj&-tw@S_K z{28g)zj)6FwSHqc`|O8G`3WM)=8AgngJMLU&i%XK=DQ>3lR^ZpC(EBXUsadS^=C~L z&nlPPbCtcP&wKrw78K&PHTrG07~4Y!^~=5UQ>%N~JY_ZQU!U3fzx$v2C-+$rpMLz& ziBo^}!!>*RwR{oti?^yj_wV|+nWwT<@TmLQ^S>@%i(9F&$fL)&=j{1awj3*0IIXly zTHCy!YEDUW)Y>TVIx#WjBGKa;KWv^=H+AVEr@G${e?*qvKh^k1#`)Ghft=kJy!Osx z|Gz@ozWebdm&=9651g0UGPU$p_Wr+LSpKX@^AnkU`Oe*orn_d8gM!qcaOw@ath#H# z*S~FC`P;Bj`t!N=C+S;jwfWx#Ou2S`oA9}necQ_)r3apgZJlZ>RwTn~uf2wS<9Ac$ zZ;LMrf7(20|I!x;tQ*5OgfIRXd8|H2T2blB6OJE|7sW*xC#$}#DsZX`>pA`XV2}6h zV(lW{@0;pN4eY(&{yOF=_P|nJ(0=vWts>4#R+SnFf8Km(&I+?_OF7MNt@it)!yO#* zAvpGfL2O;vs?UcWEiluw;;jq&u-)gT>@{Yd2r>S$$LWQ3ojrTYtL%Tg{JeRV;SKlq z`(%~nZ4FiH!jhl5mSnJamwc`&V6pF(wQ1_NSpVd)`+@UT!k;WA)!pw3Z~B%j^=FM* z*{L8guiHy^bx!=F6Kwe4q}v9z;$MGsK1dc7nPgmDcI>>@#I!5XPrh~E>Yw;W$N!&} zueuc%htv6$A25D4GLio& zT7TA9sa@V4xIFCl#m4!m-qYGXY~EM7QKfV85zSYJ&KFg6^K5&V70+>xwVdnE8n^Qe z*1g9q4h!G?5h>?>{@|ZAa}>#bLcXkJDfBuyK3N zJt(@$;_Q+?Yt~o&KJ_?rw$;lNw-1}IY%9L|BhrzPZ^_OT9#%8vKlRWLxKSgxxO$b< zwVx+{MlP6obV;c6Yw5G+5BX|t*i*$~pB)e@kobMaf()&9ZJ#%199f;j+x~mw%|(V? zigL5&hiZMw`4y^OC-&0Orr4eTN&41Z!FJbw-G9q{_-AC;Za9JUf5p# zAYD>#lK$Hula~2g3k35uSGeg-`5F22$NVU!h4zQDqZJ$vE)aq|@2 ztGloCKT3Z(hyS%Ao19DI{G(G7GX8u2(Ye*rby<7zhs`;)Ntbf^AEYN%oL(JJpm|KpW z)!p9tM`zzzEwJZiaWRYi>Tgeb6*gWI%_%tH&be#mRqK}dPv_ja-sc*d{CTs2MBMr2 z*dLKkT2|e)*>f$k_gQ-Dt?qsh(3bo&Q1lN4c)7lNNUN+<#VIzWXE6>f+1N zJFzdfx_#QbX}_xFCyuY38|EF_D`WL>vrn8b@ADgf)?B%|XkEUDefQJkc_x+X9HzfI za6U*%`eaRIdV5V~{WIntkt&QSrmuedStH?}XL@__&q$9ufzcPGPBh-Eu92|k-Xu}- zX=XC>W1j5>uMeExG`Z@N=dow$t)huN+xs7-Yr1i1B-aK$e!J`A=1Kcor@9J#*DF|c zBS7+-;2)i%+nct4obYY`|H56;_TKKx3v8@*=k}dU;eDFk`ie;~Kt+(hX~yQqA2;(@ zY3<8Njj0oRHrM^r<~`GjOWAOgK`9)LtD`K~s?^)R+J$?St#m)~K?%$J~JYV$hH@-hQ zaV;xPC5vW#Q51*R*Bhvc*+hgYsotYbalD+4yz?Ey3lhv&!%oly` z5)t)1nCH;rIW~6orz6jWzKOj2EM4@(<9VNc?070CxH00HVbgq3hy{^*phjC=U)CTH+PS<%IIGv;62GA*L++Bde&8+=F42L<~CzbIk(qoW$J zxMS+0^uW`VOJBS{a6V|t(KGXJJje~^JAD392~WpC|Ni-|uQoXaE)$C{zRG*t(EZb9 zsrOetZFYM5I^~Z}XeWc`wUr+?Z@D8o_cW8e_gld?dpf7d96A5!hNjrHr5>MI{^+>Z z3LZLj`BA#4N{8QZ>(2S9Qi6LA3;xjwPzjBiCiuB?@`ueQzBYQiobn@b-lEM~P9e|I zMSon{@^ll=-6ZlmF;m z$iJ$r^sd{G)1Ld@_D#mIbz%n#ZtRdfa{kg%jX2S}Q-4POJSXB2Tft<|=+tnVW@03|IQ_bGH^LNH2se|Vq zUEx2?f9j9Uy%mf}0wJDZ>sP5iO+Wg%<8Xo2ADzAFhHksuK5h2m(_6Y)#J;;oc;7=4 zbLR!?U)AL0`p&K~@uBYM6x#47nmWK`!OqZEnEeFm2Ht83#w zY+iQoe30#(z&ks+?N2`uuE=(XN`EcBZQ`?i9*pJN_8MB+Yx5n}{J1$lVCiN<*`JYB z+|JLx=$v}GV$U(Idn+d#Q+rcrZfWl=n(lY({Gvb8!|l?;>!z)`a!AW;xrktEPse$e zl`+CM{_LzxEUKHvpM6;K!)A+^Gu=-Ne?-m*4iRneQiuo>KQFHRZdS&FBY)PI)!MVx zO}lz%h0*?Xe{|0ODp|ftZp|Z?%)ecN(su+4nCh-6fl3dCMQ2Xwfo`x@lUfU&LHY+3dS7aP^7$suUK>P2ah4x_-=-ylo~u zOCCQ@e>=x^>z#j+HAnOBOV#Ahv%31{f{paQ?0xmxf8y=!MC)7D2QQ!YQ1oZ@Z?=k* z`uTD-+ICf+f-Wg7IIOz8zUBQdvD%tliwew^|48=Rs{X0D@Mq+v+SlC&=SRnHxu1V` z*VpxDzkj>E_Qz%M=JnA_<6GK)o{WCGao>XFx6F?_Z+ZWliT{ghjr_hvwO@{Yvui)K z*uD1swtpNJ6APH)TlW92;;z3f_x;+pe>48*Y}EN#H*x+_6OklIF{$-xJX<&ZS>ttu zt@x+i_nV<>N)-AG44ke=EIhl`)zgvHVZqXf|BZjvoO)vL%e5}dyFe&^JO7@J4di&M&)b77UjaM!H=qz+-Sy7P}U%_vBrKS6BWZSR) zd*$1V?N>Lv6We-w{l1w~-<3spX|_I`T((56d%kKY=ZED#BZICi6l;|^nzvm4nL^#P z8*x#uwxwOoczj{T`w33K zT)#QK>PE!n^69P;;nO^x#HQHy-tb4{RKx9McRCc3k6hb7DWboa!Nk1eVY+1Q$_yr# zj|^T1xp#c1OWglD%cEZI==oJzds%xQrr)&Me275`nKeA2w^L=<@Egn|SD2 zOH!Tb8-^e`&e$K3E2X{5{>+%K8Z@<9c=!Gw(KVYLBl{nu^BNc5SaL{hb;6~tbR8y9 z;UE#oz9;DuTQxs!z7mrDO;T)k__DnDP1b?W(pA+xGOh{N>IiD`DJ`7#@FVBFHFAw} ze@3!4*?io*L&4wcUwUEPo8mM#uFdO2w*3^TSZTOw>P_91DYv|n?dMg^Z*S^3dEUu? z%YpMjPo#;3+REmyf_^n#AS^cmWM;WV%lt^;Tf=^7-oSBn9ZoU%J{zr#;}pV;s_B+j_mUZJ+=QOb#kZQ395{^&%D%zU4-f5WO%dk^sWWCyL;u3Tf7^hZaS z`4BJHky$VI@x1=kv^ne3)?b!p%0&$J+26#Yo7b;y-XFew+b#L^*X-kS=X>}+o4@^2 z_HFY=-RmBGe);k0m+xACr}f3uzxdzd?yo+9Z?>IreZ^nnJ>Oo5&Ec7ME40FFKg&EW z_Dk&@^S^vPasE^BXYS8uuRHxP@Aq$6&-%mCe&YfCtoxfM%#V)9h@TWUyKcJ!zxbh# zhi5;0`|`fc-nztvw%^P@20#Cpald7AT*G3W8QpWu#ii|a_twta^e2vUd5EDxes*!m zADw&gTN^lS*y_SIt~gk^y!7^(S63Fr)${z($zQT_*Q0kQenxtIdGC9WWBQ&7hpY;X tl{TP}{4R%cHj9%Vq!-r7mMs6zU()~EglD^Y5Ca1PgQu&X%Q~loCIIUxQJ4S# literal 0 HcmV?d00001 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";