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