From d2cc440c688540f85b7bd925625397511230f307 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 2 Jun 2022 17:05:26 +0300 Subject: [PATCH 01/53] QmlDesigner: Add checkerboard background in navigator preview tooltip Checkerboard helps visualizing alpha channel in previewed images. Fixes: QDS-7060 Change-Id: Idb0139f8d8fcbf595bbfba3b333cd3d49f18d8af Reviewed-by: Thomas Hartmann Reviewed-by: Mahmoud Badri Reviewed-by: --- .../components/navigator/checkers.png | Bin 0 -> 80 bytes .../components/navigator/navigator.qrc | 1 + .../components/navigator/previewtooltip.cpp | 14 ++- .../components/navigator/previewtooltip.ui | 88 +++++++++++++----- .../instances/nodeinstanceview.cpp | 12 ++- 5 files changed, 91 insertions(+), 24 deletions(-) create mode 100644 src/plugins/qmldesigner/components/navigator/checkers.png diff --git a/src/plugins/qmldesigner/components/navigator/checkers.png b/src/plugins/qmldesigner/components/navigator/checkers.png new file mode 100644 index 0000000000000000000000000000000000000000..72cb9f0350646967cbd0bdc5c69981796f43a2dd GIT binary patch literal 80 zcmeAS@N?(olHy`uVBq!ia0y~yVBi5^4h9AWhGIEpYX$}eaZeY=5RT~N3Kq8i2OAob f_*$Ac0?#lqSbc8ylogof3sUOo>gTe~DWM4fJ|+>W literal 0 HcmV?d00001 diff --git a/src/plugins/qmldesigner/components/navigator/navigator.qrc b/src/plugins/qmldesigner/components/navigator/navigator.qrc index fca836a09be..e595bae0f9b 100644 --- a/src/plugins/qmldesigner/components/navigator/navigator.qrc +++ b/src/plugins/qmldesigner/components/navigator/navigator.qrc @@ -13,5 +13,6 @@ export_unchecked.png export_unchecked@2x.png tooltip_placeholder.png + checkers.png diff --git a/src/plugins/qmldesigner/components/navigator/previewtooltip.cpp b/src/plugins/qmldesigner/components/navigator/previewtooltip.cpp index 800104bfb30..b352f1daa21 100644 --- a/src/plugins/qmldesigner/components/navigator/previewtooltip.cpp +++ b/src/plugins/qmldesigner/components/navigator/previewtooltip.cpp @@ -28,7 +28,8 @@ #include -#include +#include +#include namespace QmlDesigner { @@ -43,6 +44,17 @@ PreviewToolTip::PreviewToolTip(QWidget *parent) m_ui->typeLabel->setElideMode(Qt::ElideLeft); m_ui->infoLabel->setElideMode(Qt::ElideLeft); setStyleSheet(QString("QWidget { background-color: %1 }").arg(Utils::creatorTheme()->color(Utils::Theme::BackgroundColorNormal).name())); + m_ui->imageLabel->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); + + static QPixmap checkers; + if (checkers.isNull()) { + checkers = {150, 150}; + QPainter painter(&checkers); + painter.setBrush(QPixmap(":/navigator/icon/checkers.png")); + painter.drawRect(0, 0, 150, 150); + } + m_ui->labelBackground->setPixmap(checkers); + } PreviewToolTip::~PreviewToolTip() diff --git a/src/plugins/qmldesigner/components/navigator/previewtooltip.ui b/src/plugins/qmldesigner/components/navigator/previewtooltip.ui index 1c06a248f03..c8520751a8e 100644 --- a/src/plugins/qmldesigner/components/navigator/previewtooltip.ui +++ b/src/plugins/qmldesigner/components/navigator/previewtooltip.ui @@ -81,28 +81,72 @@ 6 - - - - 0 - 0 - - - - - 150 - 150 - - - - QFrame::Box - - - QFrame::Plain - - - Qt::AlignCenter - + + + + + 0 + 0 + 150 + 150 + + + + + 0 + 0 + + + + + 150 + 150 + + + + QFrame::Box + + + QFrame::Plain + + + false + + + Qt::AlignCenter + + + + + + 0 + 0 + 150 + 150 + + + + + 0 + 0 + + + + + 150 + 150 + + + + QFrame::Box + + + QFrame::Plain + + + Qt::AlignCenter + + diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 29bf14d0269..db22d27fc3d 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -103,6 +103,7 @@ #include #include #include +#include #include @@ -1756,7 +1757,16 @@ void NodeInstanceView::timerEvent(QTimerEvent *event) QVariant NodeInstanceView::modelNodePreviewImageDataToVariant(const ModelNodePreviewImageData &imageData) { - static const QPixmap placeHolder(":/navigator/icon/tooltip_placeholder.png"); + static QPixmap placeHolder; + if (placeHolder.isNull()) { + QPixmap placeHolderSrc(":/navigator/icon/tooltip_placeholder.png"); + placeHolder = {150, 150}; + // Placeholder has transparency, but we don't want to show the checkerboard, so + // paint in the correct background color + placeHolder.fill(Utils::creatorTheme()->color(Utils::Theme::BackgroundColorNormal)); + QPainter painter(&placeHolder); + painter.drawPixmap(0, 0, 150, 150, placeHolderSrc); + } QVariantMap map; map.insert("type", imageData.type); From 7269aafbd8a1fddb7d43f783ac20b4d97bbc20c5 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 7 Jun 2022 13:39:25 +0300 Subject: [PATCH 02/53] QmlDesigner: Ensure material library is created when needed Added material library accessor to AbstractView, which creates the material library and moves existing materials under it in case it doesn't yet exist. Also added material assignment function to AbstractView. The reason these were added to AbstractView instead of being handled e.g. via custom notification in material editor is that they need to be called from multiple different views in the same transaction that triggers the need of material library. Fixes: QDS-7081 Change-Id: If2bb884f87d04c9f3599c2342df66ef51ec238ee Reviewed-by: Mahmoud Badri Reviewed-by: Thomas Hartmann --- .../components/edit3d/edit3dcanvas.cpp | 43 ++------- .../components/formeditor/dragtool.cpp | 15 +++ .../components/formeditor/dragtool.h | 1 + .../materialeditor/materialeditorview.cpp | 49 +--------- .../materialeditor/materialeditorview.h | 2 - .../navigator/navigatortreemodel.cpp | 83 +++++------------ .../components/navigator/navigatortreemodel.h | 1 - .../designercore/include/abstractview.h | 3 + .../designercore/model/abstractview.cpp | 92 +++++++++++++++++++ 9 files changed, 144 insertions(+), 145 deletions(-) diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp index efb9e24c1ca..02d798c1f6b 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp @@ -186,42 +186,17 @@ void Edit3DCanvas::dragEnterEvent(QDragEnterEvent *e) void Edit3DCanvas::dropEvent(QDropEvent *e) { - auto modelNode = QmlVisualNode::createQml3DNode(m_parent->view(), m_itemLibraryEntry, m_activeScene).modelNode(); - QTC_ASSERT(modelNode.isValid(), return); + m_parent->view()->executeInTransaction(__FUNCTION__, [&] { + auto modelNode = QmlVisualNode::createQml3DNode(m_parent->view(), m_itemLibraryEntry, m_activeScene).modelNode(); + QTC_ASSERT(modelNode.isValid(), return); - e->accept(); - m_parent->view()->setSelectedModelNode(modelNode); + e->accept(); + m_parent->view()->setSelectedModelNode(modelNode); - // if added node is a Model, assign it a material - if (modelNode.isSubclassOf("QtQuick3D.Model")) { - ModelNode matLib = m_parent->view()->modelNodeForId(Constants::MATERIAL_LIB_ID); - QTC_ASSERT(matLib.isValid(), return); - - const QList materials = matLib.directSubModelNodes(); - ModelNode material; - if (materials.size() > 0) { - for (const ModelNode &mat : materials) { - if (mat.isSubclassOf("QtQuick3D.Material")) { - material = mat; - break; - } - } - } - - // if no valid material, create a new default material - if (!material.isValid()) { - NodeMetaInfo metaInfo = m_parent->view()->model()->metaInfo("QtQuick3D.DefaultMaterial"); - material = m_parent->view()->createModelNode("QtQuick3D.DefaultMaterial", metaInfo.majorVersion(), - metaInfo.minorVersion()); - VariantProperty matNameProp = material.variantProperty("objectName"); - matNameProp.setValue("New Material"); - material.validId(); - matLib.defaultNodeListProperty().reparentHere(material); - } - - BindingProperty modelMatsProp = modelNode.bindingProperty("materials"); - modelMatsProp.setExpression(material.id()); - } + // if added node is a Model, assign it a material + if (modelNode.isSubclassOf("QtQuick3D.Model")) + m_parent->view()->assignMaterialTo3dModel(modelNode); + }); } void Edit3DCanvas::focusOutEvent(QFocusEvent *focusEvent) diff --git a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp index c4a64adf8f5..819bf58b34a 100644 --- a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp @@ -33,6 +33,8 @@ #include #include "qmldesignerconstants.h" +#include + #include #include #include @@ -405,10 +407,23 @@ void DragTool::move(const QPointF &scenePosition, const QList & void DragTool::commitTransaction() { try { + handleView3dDrop(); m_rewriterTransaction.commit(); } catch (const RewritingException &e) { e.showException(); } } +void DragTool::handleView3dDrop() +{ + // If a View3D is dropped, we need to assign material to the included model + for (const QmlItemNode &dragNode : qAsConst(m_dragNodes)) { + if (dragNode.modelNode().isSubclassOf("QtQuick3D.View3D")) { + const QList models = dragNode.modelNode().subModelNodesOfType("QtQuick3D.Model"); + QTC_ASSERT(models.size() == 1, return); + view()->assignMaterialTo3dModel(models.at(0)); + } + } +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/formeditor/dragtool.h b/src/plugins/qmldesigner/components/formeditor/dragtool.h index 63d3de6e8f7..bb6f5622631 100644 --- a/src/plugins/qmldesigner/components/formeditor/dragtool.h +++ b/src/plugins/qmldesigner/components/formeditor/dragtool.h @@ -90,6 +90,7 @@ protected: void move(const QPointF &scenePos, const QList &itemList); void createDragNodes(const QMimeData *mimeData, const QPointF &scenePosition, const QList &itemList); void commitTransaction(); + void handleView3dDrop(); private: MoveManipulator m_moveManipulator; diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index 29a74304d80..28c17d75611 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -78,47 +78,6 @@ MaterialEditorView::MaterialEditorView(QWidget *parent) m_stackedWidget->setMinimumWidth(250); } -void MaterialEditorView::ensureMaterialLibraryNode() -{ - if (!m_hasQuick3DImport) - return; - - m_materialLibrary = modelNodeForId(Constants::MATERIAL_LIB_ID); - if (m_materialLibrary.isValid()) - return; - - // create material library node - TypeName nodeType = rootModelNode().isSubclassOf("QtQuick3D.Node") ? "QtQuick3D.Node" : "QtQuick.Item"; - NodeMetaInfo metaInfo = model()->metaInfo(nodeType); - m_materialLibrary = createModelNode(nodeType, metaInfo.majorVersion(), metaInfo.minorVersion()); - - m_materialLibrary.setIdWithoutRefactoring(Constants::MATERIAL_LIB_ID); - rootModelNode().defaultNodeListProperty().reparentHere(m_materialLibrary); - - const QList materials = rootModelNode().subModelNodesOfType("QtQuick3D.Material"); - if (materials.isEmpty()) - return; - - RewriterTransaction transaction = beginRewriterTransaction( - "MaterialEditorView::ensureMaterialLibraryNode"); - - try { - // move all materials to under material library node - for (const ModelNode &node : materials) { - // if material has no name, set name to id - QString matName = node.variantProperty("objectName").value().toString(); - if (matName.isEmpty()) { - VariantProperty objNameProp = node.variantProperty("objectName"); - objNameProp.setValue(node.id()); - } - - m_materialLibrary.defaultNodeListProperty().reparentHere(node); - } - } catch (Exception &e) { - e.showException(); - } -} - MaterialEditorView::~MaterialEditorView() { qDeleteAll(m_qmlBackendHash); @@ -447,15 +406,13 @@ void MaterialEditorView::handleToolBarAction(int action) } case MaterialEditorContextObject::AddNewMaterial: { - ensureMaterialLibraryNode(); - executeInTransaction("MaterialEditorView:handleToolBarAction", [&] { NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.DefaultMaterial"); ModelNode newMatNode = createModelNode("QtQuick3D.DefaultMaterial", metaInfo.majorVersion(), metaInfo.minorVersion()); renameMaterial(newMatNode, "New Material"); - m_materialLibrary.defaultNodeListProperty().reparentHere(newMatNode); + materialLibraryNode().defaultNodeListProperty().reparentHere(newMatNode); }); break; } @@ -759,8 +716,6 @@ void MaterialEditorView::duplicateMaterial(const ModelNode &material) { QTC_ASSERT(material.isValid(), return); - ensureMaterialLibraryNode(); - TypeName matType = material.type(); QmlObjectNode sourceMat(material); @@ -786,7 +741,7 @@ void MaterialEditorView::duplicateMaterial(const ModelNode &material) duplicateMat.setBindingProperty(prop.name(), prop.toBindingProperty().expression()); } - m_materialLibrary.defaultNodeListProperty().reparentHere(duplicateMat); + materialLibraryNode().defaultNodeListProperty().reparentHere(duplicateMat); }); } diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h index 3e8632d26f6..d9c9e4c1ba1 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h @@ -99,7 +99,6 @@ private: void highlightSupportedProperties(bool highlight = true); QString generateIdFromName(const QString &name); - void ensureMaterialLibraryNode(); void requestPreviewRender(); void applyMaterialToSelectedModels(const ModelNode &material, bool add = false); @@ -115,7 +114,6 @@ private: bool noValidSelection() const; ModelNode m_selectedMaterial; - ModelNode m_materialLibrary; QShortcut *m_updateShortcut = nullptr; int m_timerId = 0; QStackedWidget *m_stackedWidget = nullptr; diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index 0a92f81d93f..d610014d5bf 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -686,18 +686,31 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in bool validContainer = false; ModelNode targetNode = targetProperty.parentModelNode(); - // don't allow dropping materials on any node but Models - QString itemType = QString::fromLatin1(itemLibraryEntry.typeName()); - if (itemType.startsWith("QtQuick3D.") && itemType.endsWith("Material") - && !targetNode.isSubclassOf("QtQuick3D.Model")) { - return; - } - QmlObjectNode newQmlObjectNode; m_view->executeInTransaction("NavigatorTreeModel::handleItemLibraryItemDrop", [&] { newQmlObjectNode = QmlItemNode::createQmlObjectNode(m_view, itemLibraryEntry, QPointF(), targetProperty, false); ModelNode newModelNode = newQmlObjectNode.modelNode(); if (newModelNode.isValid()) { + if (newModelNode.isSubclassOf("QtQuick3D.Material")) { + // Don't allow dropping materials on any node but Models + if (!targetNode.isSubclassOf("QtQuick3D.Model")) { + newQmlObjectNode.destroy(); + return; + } + // We can't have material initially parented if material library is created in this + // same transaction (rewriter will not allow it for some reason) + ModelNode matLib = m_view->modelNodeForId(Constants::MATERIAL_LIB_ID); + if (!matLib.isValid()) { + newQmlObjectNode.destroy(); + newQmlObjectNode = QmlItemNode::createQmlObjectNode( + m_view, itemLibraryEntry, QPointF(), NodeAbstractProperty(), false); + newModelNode = newQmlObjectNode.modelNode(); + if (!newModelNode.isValid()) + return; + } + m_view->assignMaterialTo3dModel(targetNode, newModelNode); + } + ChooseFromPropertyListDialog *dialog = ChooseFromPropertyListDialog::createIfNeeded( targetNode, newModelNode, Core::ICore::dialogParent()); if (dialog) { @@ -734,28 +747,10 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in if (newModelNode.isSubclassOf("QtQuick3D.View3D")) { const QList models = newModelNode.subModelNodesOfType("QtQuick3D.Model"); - QTC_ASSERT(models.size() == 1, return); - - assignMaterialToModel(models.at(0)); + m_view->assignMaterialTo3dModel(models.at(0)); } else if (newModelNode.isSubclassOf("QtQuick3D.Model")) { - assignMaterialToModel(newModelNode); - } - - // dropping a material on a model - if (newModelNode.isSubclassOf("QtQuick3D.Material") - && targetNode.isSubclassOf("QtQuick3D.Model")) { - // parent material to material library and assign it to target model - ModelNode matLib = m_view->modelNodeForId(Constants::MATERIAL_LIB_ID); - - QTC_ASSERT(matLib.isValid(), return); - - VariantProperty objName = newModelNode.variantProperty("objectName"); - objName.setValue("New Material"); - BindingProperty matsProp = targetNode.bindingProperty("materials"); - matsProp.setExpression(newModelNode.id()); - matLib.defaultNodeListProperty().reparentHere(newModelNode); - return; + m_view->assignMaterialTo3dModel(newModelNode); } if (!validContainer) { @@ -1089,40 +1084,6 @@ ModelNode NavigatorTreeModel::createTextureNode(const NodeAbstractProperty &targ return {}; } -// Add a material to a Quick3D.Model node -void NavigatorTreeModel::assignMaterialToModel(const ModelNode &node) -{ - ModelNode matLib = m_view->modelNodeForId(Constants::MATERIAL_LIB_ID); - - QTC_ASSERT(matLib.isValid(), return); - QTC_ASSERT(node.isSubclassOf("QtQuick3D.Model"), return); - - const QList materials = matLib.directSubModelNodes(); - ModelNode material; - if (materials.size() > 0) { - for (const ModelNode &mat : materials) { - if (mat.isSubclassOf("QtQuick3D.Material")) { - material = mat; - break; - } - } - } - - // if no valid material, create a new default material - if (!material.isValid()) { - NodeMetaInfo metaInfo = m_view->model()->metaInfo("QtQuick3D.DefaultMaterial"); - material = m_view->createModelNode("QtQuick3D.DefaultMaterial", metaInfo.majorVersion(), - metaInfo.minorVersion()); - VariantProperty matNameProp = material.variantProperty("objectName"); - matNameProp.setValue("New Material"); - material.validId(); - matLib.defaultNodeListProperty().reparentHere(material); - } - - BindingProperty modelMatsProp = node.bindingProperty("materials"); - modelMatsProp.setExpression(material.id()); -} - TypeName propertyType(const NodeAbstractProperty &property) { return property.parentModelNode().metaInfo().propertyTypeName(property.name()); diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h index 0fcb7aab3eb..96529816076 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h @@ -130,7 +130,6 @@ private: bool dropAsImage3dTexture(const ModelNode &targetNode, const NodeAbstractProperty &targetProp, const QString &imagePath, ModelNode &newNode, bool &outMoveNodesAfter); ModelNode createTextureNode(const NodeAbstractProperty &targetProp, const QString &imagePath); - void assignMaterialToModel(const ModelNode &node); QList nodesToPersistentIndex(const QList &modelNodes); void addImport(const QString &importName); QList filteredList(const NodeListProperty &property, bool filter, bool reverseOrder) const; diff --git a/src/plugins/qmldesigner/designercore/include/abstractview.h b/src/plugins/qmldesigner/designercore/include/abstractview.h index e37ed0fb4b6..18380aadb1a 100644 --- a/src/plugins/qmldesigner/designercore/include/abstractview.h +++ b/src/plugins/qmldesigner/designercore/include/abstractview.h @@ -259,6 +259,9 @@ public: void changeRootNodeType(const TypeName &type, int majorVersion, int minorVersion); + ModelNode materialLibraryNode(); + void assignMaterialTo3dModel(const ModelNode &modelNode, const ModelNode &materialNode = {}); + NodeInstanceView *nodeInstanceView() const; RewriterView *rewriterView() const; diff --git a/src/plugins/qmldesigner/designercore/model/abstractview.cpp b/src/plugins/qmldesigner/designercore/model/abstractview.cpp index 26040ba23d2..86dab970316 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractview.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractview.cpp @@ -31,6 +31,10 @@ #include "nodeinstanceview.h" #include #include +#include +#include +#include +#include #ifndef QMLDESIGNER_TEST #include @@ -805,6 +809,94 @@ void AbstractView::changeRootNodeType(const TypeName &type, int majorVersion, in m_model.data()->d->changeRootNodeType(type, majorVersion, minorVersion); } +// Returns ModelNode for project's material library. +// If the material library doesn't exist yet, it is created and all existing materials are moved +// under material library. +// This function should be called only form inside a transaction, as it potentially does many +// changes to model. +ModelNode AbstractView::materialLibraryNode() +{ + ModelNode matLib = modelNodeForId(Constants::MATERIAL_LIB_ID); + if (matLib.isValid()) + return matLib; + + // Create material library node + TypeName nodeType = rootModelNode().isSubclassOf("QtQuick3D.Node") ? "QtQuick3D.Node" + : "QtQuick.Item"; + NodeMetaInfo metaInfo = model()->metaInfo(nodeType); + matLib = createModelNode(nodeType, metaInfo.majorVersion(), metaInfo.minorVersion()); + + matLib.setIdWithoutRefactoring(Constants::MATERIAL_LIB_ID); + rootModelNode().defaultNodeListProperty().reparentHere(matLib); + + const QList materials = rootModelNode().subModelNodesOfType("QtQuick3D.Material"); + if (materials.isEmpty()) + return matLib; + + // Move all materials to under material library node + for (const ModelNode &node : materials) { + // If material has no name, set name to id + QString matName = node.variantProperty("objectName").value().toString(); + if (matName.isEmpty()) { + VariantProperty objNameProp = node.variantProperty("objectName"); + objNameProp.setValue(node.id()); + } + + matLib.defaultNodeListProperty().reparentHere(node); + } + + return matLib; +} + +// Assigns given material to a 3D model. +// The assigned material is also inserted into material library if not already there. +// If given material is not valid, first existing material from material library is used, +// or if material library is empty, a new material is created. +// This function should be called only from inside a transaction, as it potentially does many +// changes to model. +void AbstractView::assignMaterialTo3dModel(const ModelNode &modelNode, const ModelNode &materialNode) +{ + QTC_ASSERT(modelNode.isValid() && modelNode.isSubclassOf("QtQuick3D.Model"), return); + + ModelNode matLib = materialLibraryNode(); + ModelNode newMaterialNode; + + if (materialNode.isValid() && materialNode.isSubclassOf("QtQuick3D.Material")) { + newMaterialNode = materialNode; + } else { + const QList materials = matLib.directSubModelNodes(); + if (materials.size() > 0) { + for (const ModelNode &mat : materials) { + if (mat.isSubclassOf("QtQuick3D.Material")) { + newMaterialNode = mat; + break; + } + } + } + + // if no valid material, create a new default material + if (!newMaterialNode.isValid()) { + NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.DefaultMaterial"); + newMaterialNode = createModelNode("QtQuick3D.DefaultMaterial", metaInfo.majorVersion(), + metaInfo.minorVersion()); + newMaterialNode.validId(); + } + } + + QTC_ASSERT(newMaterialNode.isValid(), return); + + VariantProperty matNameProp = newMaterialNode.variantProperty("objectName"); + if (matNameProp.value().isNull()) + matNameProp.setValue("New Material"); + + if (!newMaterialNode.hasParentProperty() + || newMaterialNode.parentProperty() != matLib.defaultNodeListProperty()) { + matLib.defaultNodeListProperty().reparentHere(newMaterialNode); + } + BindingProperty modelMatsProp = modelNode.bindingProperty("materials"); + modelMatsProp.setExpression(newMaterialNode.id()); +} + ModelNode AbstractView::currentStateNode() const { if (model()) From 04b3962daf7f46e6ce053c79635eaabe6bb75f4c Mon Sep 17 00:00:00 2001 From: Brook Cronin Date: Tue, 31 May 2022 11:21:06 +0200 Subject: [PATCH 03/53] QmlDesigner: add new timeline animation icon Change-Id: I69efb2ed5a56fbf1848d00963ae70002f11b2e75 Reviewed-by: Alessandro Portale Reviewed-by: --- .../images/timeline-animation-16px.png | Bin 389 -> 296 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/plugins/qmldesigner/qtquickplugin/images/timeline-animation-16px.png b/src/plugins/qmldesigner/qtquickplugin/images/timeline-animation-16px.png index d4ecf00031f09ee20882ac301eb663031c71c0e4..31b8fed6668c63248c1edb37846174ed1895d85e 100644 GIT binary patch literal 296 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUe*XHm?$^HA3=9kvo-U3d65+9a-eN5V z9M0KWdoI6p&wf`Q>|NM6nOC22k^rxdOmw%+#b(oFh4ts{7PCEfa=kX2|CLVhx`dhk z94=gL(AdM^$J%c8BenIM_a=SLqnl0ts`}sl+4nYnxvrX?^m6X@uz0KQ&R<{c`CnLc iztv4F(qq~qwvNXfL2QfvykKBpVDNPHb6Mw<&;$VJje)iR delta 373 zcmZ3%)XF?TvYw5BfkFQB|3n4`MjuZX#}JF&-pd>PrUVMGKFBwZNLX~+p-D(6W5coo z0YL#Cy+=i)xVWXGJSrMYmg!5m>;Wxn@CQxPe&PDF;R=AC=Wd|9R_`*Y_-+ zb>@ua_cK0u&W%%oxT{~d9Px|PzqmrN{!sCsV>K=^|GgJf^4Cj#EBqDDmv!R#j3Xj( z2{QHtP0bxYO0@%=-#wOA%aXG_?B$a4-TlVt6ZV^rS#3IXHq7g#Z~TJA+c(@j_369R zCDpvHJG-Y`D%osx)g~_N)#Av@;b&Z3xRr05$l~7+Hm^|jt&XX*;PS}5jmc+1S8iUm zTjKnq?m0YyAKmjGZvVUVKwhYry`QG7QHOeDO)S?FeZGaUH{bOeG>1o?_uSds{-bH5 zPHl^`&CzV0^sP^=nWml)vbz$Y|51|rDTmS{<$F?lsy*YnI^H!jpE+r~CTZiQ`nBK1 g? Date: Wed, 8 Jun 2022 14:08:50 +0200 Subject: [PATCH 04/53] QmlDesigner: Make text label visible in 3d wizard Task-number: QDS-7054 Change-Id: I44a2d01c799b619b553c6b1e76c003324e7b80ba Reviewed-by: Thomas Hartmann --- .../projects/application-3d/Screen01.ui.qml.tpl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/Screen01.ui.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/Screen01.ui.qml.tpl index 338621b72e0..24566ef2cc2 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/Screen01.ui.qml.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/Screen01.ui.qml.tpl @@ -16,12 +16,6 @@ Rectangle { color: Constants.backgroundColor - Text { - text: qsTr("Hello %{ProjectName}") - anchors.centerIn: parent - font.family: Constants.font.family - } - View3D { id: view3D anchors.fill: parent @@ -62,5 +56,12 @@ Rectangle { objectName: "Default Material" diffuseColor: "#4aee45" } + + Text { + text: qsTr("Hello %{ProjectName}") + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 100 + font.family: Constants.font.family } } From 55cf108f96564304db9b1b4951b93f24279451d6 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 8 Jun 2022 15:05:53 +0200 Subject: [PATCH 05/53] QmlDesigner: Update QDS version in template Change-Id: I0bfbd469d49568ec2be0b6b936c46bbc9160fbcb Reviewed-by: Thomas Hartmann --- .../studio_templates/projects/common/app.qmlproject.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl index 3d2490e8d9c..6b2195d6d1c 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/app.qmlproject.tpl @@ -90,7 +90,7 @@ Project { /* Required for deployment */ targetDirectory: "/opt/%{ProjectName}" - qdsVersion: "3.4" + qdsVersion: "3.5" quickVersion: "%{QtQuickVersion}" From 6d1478e1db134d37935b1c65adb226edf60e9f6e Mon Sep 17 00:00:00 2001 From: Artem Sokolovskii Date: Fri, 3 Jun 2022 15:10:46 +0200 Subject: [PATCH 06/53] Clang Format: Fix misplaced cursor after undo Fixed behavior, when after undo function cursor, jumps to the start of a file. Join the undo block generated by auto format with the last available undo block. This will place the cursor at the last edit position instead of the document beginning when triggering undo. Fixes: QTCREATORBUG-27608 Change-Id: I1bb630af00e997ac53f178594445293ceebcfa26 Reviewed-by: Reviewed-by: David Schulz --- src/plugins/cppeditor/cppeditordocument.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/cppeditor/cppeditordocument.cpp b/src/plugins/cppeditor/cppeditordocument.cpp index e79d8a17675..f8214401262 100644 --- a/src/plugins/cppeditor/cppeditordocument.cpp +++ b/src/plugins/cppeditor/cppeditordocument.cpp @@ -487,7 +487,7 @@ bool CppEditorDocument::save(QString *errorString, const Utils::FilePath &filePa if (!editedRanges.empty()) { QTextCursor cursor(document()); - cursor.beginEditBlock(); + cursor.joinPreviousEditBlock(); indenter()->format(editedRanges); cursor.endEditBlock(); } From 2e5892fbf0db0397b77556d3145faa68f8be11a2 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Wed, 8 Jun 2022 14:58:28 +0200 Subject: [PATCH 07/53] QmlDesigner: Fix node component missing state prop Fix node components not showing the states property in the property editor. This is caused by querying majorVersion instead of majorQtQuickVersion. Task-number: QDS-6981 Change-Id: I0a87d921ce985f7fd58b92f526531c49622bb235 Reviewed-by: Mahmoud Badri Reviewed-by: --- .../propertyEditorQmlSources/QtQuick3D/Object3DPane.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml index 7cded878a1d..1a17b828081 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick3D/Object3DPane.qml @@ -33,7 +33,7 @@ PropertyEditorPane { id: itemPane ComponentSection { - showState: majorVersion >= 6 + showState: majorQtQuickVersion >= 6 } Column { From 1c6fa836480808a9fee2ddd9461fc4ad2c4b3058 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Mon, 23 May 2022 00:04:31 +0200 Subject: [PATCH 08/53] QmlDesigner: Change FileResourcesModel Change FileResourcesModel to have one list of FileResourcesItem with properties absoluteFilePath, relativeFilePath and fileName instead of having two separated lists. Change-Id: Ib4b4884a6739658434844d2adb9c05c3871104e7 Reviewed-by: Thomas Hartmann Reviewed-by: Qt CI Bot --- .../imports/HelperWidgets/FontComboBox.qml | 4 +- .../imports/HelperWidgets/UrlChooser.qml | 33 +++++--- .../propertyeditor/fileresourcesmodel.cpp | 81 ++++++++++++------- .../propertyeditor/fileresourcesmodel.h | 54 ++++++++++--- 4 files changed, 115 insertions(+), 57 deletions(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/FontComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/FontComboBox.qml index 4b5f5caf4ac..6d9f56b178c 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/FontComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/FontComboBox.qml @@ -55,8 +55,8 @@ StudioControls.ComboBox { function setupModel() { var familyNames = ["Arial", "Times New Roman", "Courier", "Verdana", "Tahoma"] // default fonts - for (var i = 0; i < fileModel.fullPathModel.length; ++i) { // add custom fonts - var fontLoader = createFontLoader(fileModel.docPath + "/" + fileModel.fullPathModel[i]) + for (var i = 0; i < fileModel.model.length; ++i) { // add custom fonts + var fontLoader = createFontLoader(fileModel.docPath + "/" + fileModel.model[i].relativeFilePath) familyNames.push(fontLoader.name) } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml index f6766234d06..ee80d95a969 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml @@ -88,7 +88,8 @@ Row { } delegate: ItemDelegate { - required property string fullPath + required property string absoluteFilePath + required property string relativeFilePath required property string name required property int group required property int index @@ -150,9 +151,9 @@ Row { } ToolTip { - id: itemToolTip - visible: delegateRoot.hovered && comboBox.highlightedIndex === index - text: fullPath + id: delegateToolTip + visible: delegateRoot.hovered + text: delegateRoot.relativeFilePath delay: StudioTheme.Values.toolTipDelay height: StudioTheme.Values.toolTipHeight background: Rectangle { @@ -197,8 +198,9 @@ Row { if (root.backendValue.isBound) { comboBox.textValue = root.backendValue.expression } else { - var fullPath = root.backendValue.valueToString - comboBox.textValue = fullPath.substr(fullPath.lastIndexOf('/') + 1) + // Can be absolute or relative file path + var filePath = root.backendValue.valueToString + comboBox.textValue = filePath.substr(filePath.lastIndexOf('/') + 1) } comboBox.setCurrentText(comboBox.textValue) @@ -230,7 +232,7 @@ Row { // Check if value set by user matches with a name in the model then pick the full path let index = comboBox.find(inputValue) if (index !== -1) - inputValue = comboBox.items.get(index).model.fullPath + inputValue = comboBox.items.get(index).model.relativeFilePath root.backendValue.value = inputValue comboBox.dirty = false @@ -252,7 +254,7 @@ Row { let inputValue = comboBox.editText if (index >= 0) - inputValue = comboBox.items.get(index).model.fullPath + inputValue = comboBox.items.get(index).model.relativeFilePath if (root.backendValue.value !== inputValue) root.backendValue.value = inputValue @@ -284,17 +286,22 @@ Row { if (root.defaultItems !== undefined) { for (var i = 0; i < root.defaultItems.length; ++i) { comboBox.listModel.append({ - fullPath: root.defaultItems[i], + absoluteFilePath: "", + relativeFilePath: root.defaultItems[i], name: root.defaultItems[i], group: 0 }) } } - for (var j = 0; j < fileModel.fullPathModel.length; ++j) { + const myModel = fileModel.model + for (var j = 0; j < myModel.length; ++j) { + let item = myModel[j] + comboBox.listModel.append({ - fullPath: fileModel.fullPathModel[j], - name: fileModel.fileNameModel[j], + absoluteFilePath: item.absoluteFilePath, + relativeFilePath: item.relativeFilePath, + name: item.fileName, group: 1 }) } @@ -304,7 +311,7 @@ Row { Connections { target: fileModel - function onFullPathModelChanged() { + function onModelChanged() { root.createModel() comboBox.setCurrentText(comboBox.textValue) } diff --git a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp index e15ff49744e..18fcba3efc2 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp @@ -29,11 +29,11 @@ #include -#include #include +#include -#include #include +#include #include #include @@ -46,11 +46,13 @@ FileResourcesModel::FileResourcesModel(QObject *parent) , m_filter(QLatin1String("(*.*)")) { ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile( - QmlDesigner::DocumentManager::currentFilePath()); + QmlDesigner::DocumentManager::currentFilePath()); if (project) { - connect(project, &ProjectExplorer::Project::fileListChanged, - this, &FileResourcesModel::refreshModel); + connect(project, + &ProjectExplorer::Project::fileListChanged, + this, + &FileResourcesModel::refreshModel); } } @@ -58,14 +60,14 @@ void FileResourcesModel::setModelNodeBackend(const QVariant &modelNodeBackend) { auto modelNodeBackendObject = modelNodeBackend.value(); - const auto backendObjectCasted = - qobject_cast(modelNodeBackendObject); + const auto backendObjectCasted = qobject_cast( + modelNodeBackendObject); if (backendObjectCasted) { QmlDesigner::Model *model = backendObjectCasted->qmlObjectNode().modelNode().model(); m_docPath = QDir{QFileInfo{model->fileUrl().toLocalFile()}.absolutePath()}; - m_path = QUrl::fromLocalFile(QmlDesigner::DocumentManager::currentProjectDirPath() - .toFileInfo().absoluteFilePath()); + m_path = QUrl::fromLocalFile( + QmlDesigner::DocumentManager::currentProjectDirPath().toFileInfo().absoluteFilePath()); } setupModel(); @@ -96,6 +98,8 @@ void FileResourcesModel::setPath(const QUrl &url) { m_path = url; setupModel(); + + emit pathChanged(url); } QUrl FileResourcesModel::path() const @@ -110,10 +114,13 @@ QUrl FileResourcesModel::docPath() const void FileResourcesModel::setFilter(const QString &filter) { - if (m_filter != filter) { - m_filter = filter; - setupModel(); - } + if (m_filter == filter) + return; + + m_filter = filter; + setupModel(); + + emit filterChanged(filter); } QString FileResourcesModel::filter() const @@ -121,14 +128,9 @@ QString FileResourcesModel::filter() const return m_filter; } -QStringList FileResourcesModel::fullPathModel() const +QList FileResourcesModel::model() const { - return m_fullPathModel; -} - -QStringList FileResourcesModel::fileNameModel() const -{ - return m_fileNameModel; + return m_model; } void FileResourcesModel::openFileDialog() @@ -164,6 +166,25 @@ void FileResourcesModel::openFileDialog() } } +QString FileResourcesModel::resolve(const QString &relative) const +{ + if (relative.startsWith('#')) + return relative; + + if (QDir::isAbsolutePath(relative)) + return relative; + + if (!QUrl::fromUserInput(relative, m_docPath.path()).isLocalFile()) + return relative; + + return QFileInfo(m_docPath, relative).absoluteFilePath(); +} + +bool FileResourcesModel::isLocal(const QString &path) const +{ + return QUrl::fromUserInput(path, m_docPath.path()).isLocalFile(); +} + void FileResourcesModel::registerDeclarativeType() { qmlRegisterType("HelperWidgets", 2, 0, "FileResourcesModel"); @@ -207,8 +228,7 @@ void FileResourcesModel::setupModel() void FileResourcesModel::refreshModel() { - m_fullPathModel.clear(); - m_fileNameModel.clear(); + m_model.clear(); QStringList filterList = m_filter.split(QLatin1Char(' ')); @@ -216,18 +236,17 @@ void FileResourcesModel::refreshModel() while (it.hasNext()) { QString absolutePath = it.next(); if (filterMetaIcons(absolutePath)) { - QString filePath = m_docPath.relativeFilePath(absolutePath); - m_fullPathModel.append(filePath); + QString relativeFilePath = m_docPath.relativeFilePath(absolutePath); + m_model.append( + FileResourcesItem(absolutePath, + relativeFilePath, + relativeFilePath.mid(relativeFilePath.lastIndexOf('/') + 1))); } } - Utils::sort(m_fullPathModel, [](const QString &s1, const QString &s2) { - return s1.mid(s1.lastIndexOf('/') + 1).toLower() < s2.mid(s2.lastIndexOf('/') + 1).toLower(); + Utils::sort(m_model, [](const FileResourcesItem &i1, const FileResourcesItem &i2) { + return i1.fileName().toLower() < i2.fileName().toLower(); }); - for (const QString &fullPath : qAsConst(m_fullPathModel)) - m_fileNameModel.append(fullPath.mid(fullPath.lastIndexOf('/') + 1)); - - emit fullPathModelChanged(); - emit fileNameModelChanged(); + emit modelChanged(); } diff --git a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h index f0380c5a52e..5f49c936cb7 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h +++ b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h @@ -33,17 +33,47 @@ #include #include +class FileResourcesItem +{ + Q_GADGET + + Q_PROPERTY(QString absoluteFilePath READ absoluteFilePath CONSTANT) + Q_PROPERTY(QString relativeFilePath READ relativeFilePath CONSTANT) + Q_PROPERTY(QString fileName READ fileName CONSTANT) + +public: + FileResourcesItem(const QString &absoluteFilePath, + const QString &relativeFilePath, + const QString &fileName) + : m_absoluteFilePath(absoluteFilePath) + , m_relativeFilePath(relativeFilePath) + , m_fileName(fileName) + {} + + FileResourcesItem() = default; + FileResourcesItem(const FileResourcesItem &other) = default; + FileResourcesItem &operator=(const FileResourcesItem &other) = default; + + const QString &absoluteFilePath() const { return m_absoluteFilePath; } + const QString &relativeFilePath() const { return m_relativeFilePath; } + const QString &fileName() const { return m_fileName; } + +private: + QString m_absoluteFilePath; + QString m_relativeFilePath; + QString m_fileName; +}; + class FileResourcesModel : public QObject { Q_OBJECT Q_PROPERTY(QString fileName READ fileName WRITE setFileNameStr NOTIFY fileNameChanged) - Q_PROPERTY(QString filter READ filter WRITE setFilter) + Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged) Q_PROPERTY(QVariant modelNodeBackendProperty READ modelNodeBackend WRITE setModelNodeBackend NOTIFY modelNodeBackendChanged) - Q_PROPERTY(QUrl path READ path WRITE setPath) - Q_PROPERTY(QUrl docPath READ docPath) - Q_PROPERTY(QStringList fullPathModel READ fullPathModel NOTIFY fullPathModelChanged) - Q_PROPERTY(QStringList fileNameModel READ fileNameModel NOTIFY fileNameModelChanged) + Q_PROPERTY(QUrl path READ path WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QUrl docPath READ docPath CONSTANT) + Q_PROPERTY(QList model READ model NOTIFY modelChanged) public: explicit FileResourcesModel(QObject *parent = nullptr); @@ -57,20 +87,23 @@ public: QUrl docPath() const; void setFilter(const QString &filter); QString filter() const; - QStringList fullPathModel() const; - QStringList fileNameModel() const; + QList model() const; + void setupModel(); void refreshModel(); Q_INVOKABLE void openFileDialog(); + Q_INVOKABLE QString resolve(const QString &relative) const; + Q_INVOKABLE bool isLocal(const QString &path) const; static void registerDeclarativeType(); signals: void fileNameChanged(const QUrl &fileName); + void filterChanged(const QString &filte); void modelNodeBackendChanged(); - void fullPathModelChanged(); - void fileNameModelChanged(); + void pathChanged(const QUrl &path); + void modelChanged(); private: QVariant modelNodeBackend() const; @@ -83,8 +116,7 @@ private: QString m_filter; QString m_currentPath; QString m_lastResourcePath; - QStringList m_fullPathModel; - QStringList m_fileNameModel; + QList m_model; }; QML_DECLARE_TYPE(FileResourcesModel) From bd51b4fdc2eb080b5883ebecea4a31320aa6ae2d Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Fri, 3 Jun 2022 14:34:11 +0200 Subject: [PATCH 09/53] QmlDesigner: Add tooltips to UrlChooser * Add tooltips with thumbnails to UrlChooser enable preview of image formats and meshes * Add property editor image provider which makes use of the image cache * Add mesh image cache collector in order to create thumbnails for meshes and built-in primitves * Fix typo in explicit image cache image provider * Add return value in time stamp provider if provided file does not exist Change-Id: I2290d2ace87ddd90e9899e343f2ad1ecd2993fdf Reviewed-by: Reviewed-by: Thomas Hartmann --- .../imports/HelperWidgets/UrlChooser.qml | 150 ++++++++++++++++-- src/plugins/qmldesigner/CMakeLists.txt | 4 +- .../propertyeditorimageprovider.cpp | 68 ++++++++ .../propertyeditorimageprovider.h | 48 ++++++ .../propertyeditorqmlbackend.cpp | 13 +- .../propertyeditor/propertyeditorqmlbackend.h | 3 +- .../propertyeditor/propertyeditorview.cpp | 24 +-- .../propertyeditor/propertyeditorview.h | 4 +- .../quick2propertyeditorview.cpp | 7 +- .../propertyeditor/quick2propertyeditorview.h | 2 +- .../explicitimagecacheimageprovider.cpp | 16 +- .../imagecache/meshimagecachecollector.cpp | 112 +++++++++++++ .../imagecache/meshimagecachecollector.h | 70 ++++++++ .../imagecache/smallimagecacheprovider.cpp | 87 ++++++++++ .../imagecache/smallimagecacheprovider.h | 68 ++++++++ .../imagecache/timestampprovider.cpp | 8 +- .../designercore/include/viewmanager.h | 3 +- .../designercore/model/viewmanager.cpp | 7 +- src/plugins/qmldesigner/qmldesignercore.cmake | 2 + src/plugins/qmldesigner/qmldesignerplugin.cpp | 3 +- src/plugins/qmldesigner/qmldesignerplugin.qbs | 6 + .../qmldesigner/qmldesignerprojectmanager.cpp | 33 ++-- .../qmldesigner/qmldesignerprojectmanager.h | 1 + 23 files changed, 682 insertions(+), 57 deletions(-) create mode 100644 src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.cpp create mode 100644 src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.h create mode 100644 src/plugins/qmldesigner/designercore/imagecache/meshimagecachecollector.cpp create mode 100644 src/plugins/qmldesigner/designercore/imagecache/meshimagecachecollector.h create mode 100644 src/plugins/qmldesigner/designercore/imagecache/smallimagecacheprovider.cpp create mode 100644 src/plugins/qmldesigner/designercore/imagecache/smallimagecacheprovider.h diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml index ee80d95a969..ec246f56f28 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml @@ -44,6 +44,9 @@ Row { // by QtQuick3D to add built-in primitives to the model. property var defaultItems + // Current item + property string absoluteFilePath: "" + FileResourcesModel { id: fileModel modelNodeBackendProperty: modelNodeBackend @@ -74,16 +77,64 @@ Row { visible: comboBox.hover && toolTip.text !== "" text: root.backendValue.valueToString delay: StudioTheme.Values.toolTipDelay - height: StudioTheme.Values.toolTipHeight + background: Rectangle { color: StudioTheme.Values.themeToolTipBackground border.color: StudioTheme.Values.themeToolTipOutline border.width: StudioTheme.Values.border } - contentItem: Text { - color: StudioTheme.Values.themeToolTipText - text: toolTip.text - verticalAlignment: Text.AlignVCenter + + contentItem: RowLayout { + spacing: 10 + + Item { + visible: thumbnail.status === Image.Ready + Layout.preferredWidth: 100 + Layout.preferredHeight: 100 + + Image { + id: checker + visible: !root.isMesh(root.absoluteFilePath) + anchors.fill: parent + fillMode: Image.Tile + source: "images/checkers.png" + } + + Image { + id: thumbnail + asynchronous: true + anchors.fill: parent + fillMode: Image.PreserveAspectFit + source: { + if (root.isBuiltInPrimitive(root.absoluteFilePath)) + return "image://qmldesigner_thumbnails/" + + root.absoluteFilePath.substring(1, root.absoluteFilePath.length) + + ".builtin" + + if (fileModel.isLocal(root.absoluteFilePath)) + return "image://qmldesigner_thumbnails/" + root.absoluteFilePath + + return root.absoluteFilePath + } + } + } + + ColumnLayout { + Text { + text: root.fileName(toolTip.text) + color: StudioTheme.Values.themeToolTipText + font: toolTip.font + } + + Text { + Layout.fillWidth: true + text: root.isBuiltInPrimitive(toolTip.text) ? qsTr("Built-in primitive") + : toolTip.text + font: toolTip.font + color: StudioTheme.Values.themeToolTipText + wrapMode: Text.WordWrap + } + } } } @@ -155,16 +206,62 @@ Row { visible: delegateRoot.hovered text: delegateRoot.relativeFilePath delay: StudioTheme.Values.toolTipDelay - height: StudioTheme.Values.toolTipHeight + background: Rectangle { color: StudioTheme.Values.themeToolTipBackground border.color: StudioTheme.Values.themeToolTipOutline border.width: StudioTheme.Values.border } - contentItem: Text { - color: StudioTheme.Values.themeToolTipText - text: itemToolTip.text - verticalAlignment: Text.AlignVCenter + + contentItem: RowLayout { + spacing: 10 + + Item { + visible: delegateThumbnail.status === Image.Ready + Layout.preferredWidth: 100 + Layout.preferredHeight: 100 + + Image { + id: delegateChecker + visible: !root.isMesh(delegateRoot.absoluteFilePath) + anchors.fill: parent + fillMode: Image.Tile + source: "images/checkers.png" + } + + Image { + id: delegateThumbnail + asynchronous: true + anchors.fill: parent + fillMode: Image.PreserveAspectFit + source: { + if (root.isBuiltInPrimitive(delegateRoot.name)) + return "image://qmldesigner_thumbnails/" + + delegateRoot.name.substring(1, delegateRoot.name.length) + + ".builtin" + + return "image://qmldesigner_thumbnails/" + delegateRoot.absoluteFilePath + } + } + } + + ColumnLayout { + Text { + text: delegateRoot.name + color: StudioTheme.Values.themeToolTipText + font: delegateToolTip.font + } + + Text { + Layout.fillWidth: true + text: root.isBuiltInPrimitive(delegateToolTip.text) + ? qsTr("Built-in primitive") + : delegateToolTip.text + font: delegateToolTip.font + color: StudioTheme.Values.themeToolTipText + wrapMode: Text.WordWrap + } + } } } } @@ -235,6 +332,10 @@ Row { inputValue = comboBox.items.get(index).model.relativeFilePath root.backendValue.value = inputValue + + if (!root.backendValue.isBound) + root.absoluteFilePath = fileModel.resolve(root.backendValue.value) + comboBox.dirty = false } @@ -259,6 +360,9 @@ Row { if (root.backendValue.value !== inputValue) root.backendValue.value = inputValue + if (!root.backendValue.isBound) + root.absoluteFilePath = fileModel.resolve(root.backendValue.value) + comboBox.dirty = false } @@ -275,6 +379,23 @@ Row { } } + function isBuiltInPrimitive(value) { + return value.startsWith('#') + } + + function isMesh(value) { + return root.isBuiltInPrimitive(value) + || root.hasFileExtension(root.fileName(value), "mesh") + } + + function hasFileExtension(fileName, extension) { + return fileName.split('.').pop() === extension + } + + function fileName(filePath) { + return filePath.substr(filePath.lastIndexOf('/') + 1) + } + function createModel() { // Build the combobox model comboBox.listModel.clear() @@ -322,6 +443,9 @@ Row { Component.onCompleted: { root.createModel() comboBox.updateTextValue() + + if (!root.backendValue.isBound) + root.absoluteFilePath = fileModel.resolve(root.backendValue.value) } function indexOf(model, criteria) { @@ -340,7 +464,7 @@ Row { if (comboBox.popup.opened && !root.backendValue.isBound) { var index = root.indexOf(comboBox.items, function(item) { - return item.fullPath === root.backendValue.value + return item.relativeFilePath === root.backendValue.value }) if (index !== -1) { @@ -359,8 +483,10 @@ Row { iconColor: root.textColor onClicked: { fileModel.openFileDialog() - if (fileModel.fileName !== "") + if (fileModel.fileName !== "") { root.backendValue.value = fileModel.fileName + root.absoluteFilePath = fileModel.resolve(root.backendValue.value) + } } } } diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 1d56e323e9a..1ac2f477bf3 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -302,6 +302,7 @@ extend_qtc_plugin(QmlDesigner gradientpresetitem.cpp gradientpresetitem.h gradientpresetlistmodel.cpp gradientpresetlistmodel.h propertyeditorcontextobject.cpp propertyeditorcontextobject.h + propertyeditorimageprovider.cpp propertyeditorimageprovider.h propertyeditorqmlbackend.cpp propertyeditorqmlbackend.h propertyeditortransaction.cpp propertyeditortransaction.h propertyeditorvalue.cpp propertyeditorvalue.h @@ -389,7 +390,8 @@ extend_qtc_plugin(QmlDesigner SOURCES explicitimagecacheimageprovider.cpp explicitimagecacheimageprovider.h - + smallimagecacheprovider.cpp + smallimagecacheprovider.h ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.cpp new file mode 100644 index 00000000000..2037e1509e9 --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.cpp @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "propertyeditorimageprovider.h" +#include "assetslibrarymodel.h" + +#include +#include +#include + +#include +#include + +namespace QmlDesigner { + +QQuickImageResponse *PropertyEditorImageProvider::requestImageResponse(const QString &id, + const QSize &requestedSize) +{ + const QString suffix = "*." + id.split('.').last().toLower(); + + if (suffix == "*.mesh") + return m_smallImageCacheProvider.requestImageResponse(id, requestedSize); + + if (suffix == "*.builtin") + return m_smallImageCacheProvider.requestImageResponse("#" + id.split('.').first(), + requestedSize); + + QImage image; + auto response = std::make_unique(image); + + QMetaObject::invokeMethod( + response.get(), + [response = QPointer(response.get()), image, suffix, id] { + if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix)) + response->setImage(QImage(Utils::StyleHelper::dpiSpecificImageFile(id))); + else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix)) + response->setImage(HdrImage{id}.image()); + else + response->abort(); + }, + Qt::QueuedConnection); + + return response.release(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.h new file mode 100644 index 00000000000..bb883e44504 --- /dev/null +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "imagecache/smallimagecacheprovider.h" + +#include + +namespace QmlDesigner { + +class PropertyEditorImageProvider : public QQuickAsyncImageProvider +{ +public: + PropertyEditorImageProvider(AsynchronousImageCache &imageCache, const QImage &defaultImage = {}) + : m_smallImageCacheProvider(imageCache, defaultImage) + {} + + QQuickImageResponse *requestImageResponse(const QString &id, + const QSize &requestedSize) override; + +private: + SmallImageCacheProvider m_smallImageCacheProvider; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index c73290bf76a..d00957fa8cf 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -95,9 +95,12 @@ static QObject *variantToQObject(const QVariant &value) namespace QmlDesigner { -PropertyEditorQmlBackend::PropertyEditorQmlBackend(PropertyEditorView *propertyEditor) : - m_view(new Quick2PropertyEditorView), m_propertyEditorTransaction(new PropertyEditorTransaction(propertyEditor)), m_dummyPropertyEditorValue(new PropertyEditorValue()), - m_contextObject(new PropertyEditorContextObject()) +PropertyEditorQmlBackend::PropertyEditorQmlBackend(PropertyEditorView *propertyEditor, + AsynchronousImageCache &imageCache) + : m_view(new Quick2PropertyEditorView(imageCache)) + , m_propertyEditorTransaction(new PropertyEditorTransaction(propertyEditor)) + , m_dummyPropertyEditorValue(new PropertyEditorValue()) + , m_contextObject(new PropertyEditorContextObject()) { m_view->engine()->setOutputWarningsToStandardError(QmlDesignerPlugin::instance() ->settings().value(DesignerSettingsKey::SHOW_PROPERTYEDITOR_WARNINGS).toBool()); @@ -115,7 +118,9 @@ PropertyEditorQmlBackend::PropertyEditorQmlBackend(PropertyEditorView *propertyE PropertyEditorQmlBackend::~PropertyEditorQmlBackend() = default; -void PropertyEditorQmlBackend::setupPropertyEditorValue(const PropertyName &name, PropertyEditorView *propertyEditor, const QString &type) +void PropertyEditorQmlBackend::setupPropertyEditorValue(const PropertyName &name, + PropertyEditorView *propertyEditor, + const QString &type) { QmlDesigner::PropertyName propertyName(name); propertyName.replace('.', '_'); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h index 983a917f2fd..7abfc550d87 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h @@ -50,7 +50,8 @@ class PropertyEditorQmlBackend public: - PropertyEditorQmlBackend(PropertyEditorView *propertyEditor); + PropertyEditorQmlBackend(PropertyEditorView *propertyEditor, + class AsynchronousImageCache &imageCache); ~PropertyEditorQmlBackend(); void setup(const QmlObjectNode &fxObjectNode, const QString &stateName, const QUrl &qmlSpecificsFile, PropertyEditorView *propertyEditor); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index 7f190b62888..586c4dec054 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -69,16 +69,16 @@ static bool propertyIsAttachedLayoutProperty(const PropertyName &propertyName) return propertyName.contains("Layout."); } -PropertyEditorView::PropertyEditorView(QWidget *parent) : - AbstractView(parent), - m_parent(parent), - m_updateShortcut(nullptr), - m_timerId(0), - m_stackedWidget(new PropertyEditorWidget(parent)), - m_qmlBackEndForCurrentType(nullptr), - m_locked(false), - m_setupCompleted(false), - m_singleShotTimer(new QTimer(this)) +PropertyEditorView::PropertyEditorView(AsynchronousImageCache &imageCache) + : AbstractView() + , m_imageCache(imageCache) + , m_updateShortcut(nullptr) + , m_timerId(0) + , m_stackedWidget(new PropertyEditorWidget()) + , m_qmlBackEndForCurrentType(nullptr) + , m_locked(false) + , m_setupCompleted(false) + , m_singleShotTimer(new QTimer(this)) { m_qmlDir = PropertyEditorQmlBackend::propertyEditorResourcesPath(); @@ -117,7 +117,7 @@ void PropertyEditorView::setupPane(const TypeName &typeName) PropertyEditorQmlBackend *qmlBackend = m_qmlBackendHash.value(qmlFile.toString()); if (!qmlBackend) { - qmlBackend = new PropertyEditorQmlBackend(this); + qmlBackend = new PropertyEditorQmlBackend(this, m_imageCache); qmlBackend->initialSetup(typeName, qmlSpecificsFile, this); qmlBackend->setSource(qmlFile); @@ -484,7 +484,7 @@ void PropertyEditorView::setupQmlBackend() QString currentStateName = currentState().isBaseState() ? currentState().name() : QStringLiteral("invalid state"); if (!currentQmlBackend) { - currentQmlBackend = new PropertyEditorQmlBackend(this); + currentQmlBackend = new PropertyEditorQmlBackend(this, m_imageCache); m_stackedWidget->addWidget(currentQmlBackend->widget()); m_qmlBackendHash.insert(qmlFile.toString(), currentQmlBackend); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h index 06e86fd57cd..3bbe5020506 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h @@ -51,7 +51,7 @@ class PropertyEditorView: public AbstractView Q_OBJECT public: - PropertyEditorView(QWidget *parent = nullptr); + PropertyEditorView(class AsynchronousImageCache &imageCache); ~PropertyEditorView() override; bool hasWidget() const override; @@ -119,8 +119,8 @@ private: //functions bool noValidSelection() const; private: //variables + AsynchronousImageCache &m_imageCache; ModelNode m_selectedNode; - QWidget *m_parent; QShortcut *m_updateShortcut; int m_timerId; PropertyEditorWidget* m_stackedWidget; diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp index 35b74111d1e..08358353c10 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp @@ -36,6 +36,7 @@ #include "gradientpresetdefaultlistmodel.h" #include "itemfiltermodel.h" #include "propertyeditorcontextobject.h" +#include "propertyeditorimageprovider.h" #include "propertyeditorqmlbackend.h" #include "propertyeditorvalue.h" #include "qmlanchorbindingproxy.h" @@ -45,11 +46,13 @@ namespace QmlDesigner { -Quick2PropertyEditorView::Quick2PropertyEditorView(QWidget *parent) : - QQuickWidget(parent) +Quick2PropertyEditorView::Quick2PropertyEditorView(AsynchronousImageCache &imageCache) + : QQuickWidget() { setResizeMode(QQuickWidget::SizeRootObjectToView); Theme::setupTheme(engine()); + engine()->addImageProvider("qmldesigner_thumbnails", + new PropertyEditorImageProvider(imageCache)); } void Quick2PropertyEditorView::registerQmlTypes() diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.h b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.h index 7bfc6f15580..ca92f2a6efd 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.h +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.h @@ -35,7 +35,7 @@ class Quick2PropertyEditorView : public QQuickWidget Q_OBJECT public: - explicit Quick2PropertyEditorView(QWidget *parent = nullptr); + explicit Quick2PropertyEditorView(class AsynchronousImageCache &imageCache); static void registerQmlTypes(); }; diff --git a/src/plugins/qmldesigner/designercore/imagecache/explicitimagecacheimageprovider.cpp b/src/plugins/qmldesigner/designercore/imagecache/explicitimagecacheimageprovider.cpp index d94221f3826..57a96f1c1c1 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/explicitimagecacheimageprovider.cpp +++ b/src/plugins/qmldesigner/designercore/imagecache/explicitimagecacheimageprovider.cpp @@ -30,12 +30,12 @@ #include #include -namespace QmlDesigner { +namespace { -class ImageRespose : public QQuickImageResponse +class ImageResponse : public QQuickImageResponse { public: - ImageRespose(const QImage &defaultImage) + ImageResponse(const QImage &defaultImage) : m_image(defaultImage) {} @@ -57,14 +57,18 @@ private: QImage m_image; }; +} // namespace + +namespace QmlDesigner { + QQuickImageResponse *ExplicitImageCacheImageProvider::requestImageResponse(const QString &id, const QSize &) { - auto response = std::make_unique(m_defaultImage); + auto response = std::make_unique<::ImageResponse>(m_defaultImage); m_cache.requestImage( id, - [response = QPointer(response.get())](const QImage &image) { + [response = QPointer<::ImageResponse>(response.get())](const QImage &image) { QMetaObject::invokeMethod( response, [response, image] { @@ -73,7 +77,7 @@ QQuickImageResponse *ExplicitImageCacheImageProvider::requestImageResponse(const }, Qt::QueuedConnection); }, - [response = QPointer(response.get())](ImageCache::AbortReason abortReason) { + [response = QPointer<::ImageResponse>(response.get())](ImageCache::AbortReason abortReason) { QMetaObject::invokeMethod( response, [response, abortReason] { diff --git a/src/plugins/qmldesigner/designercore/imagecache/meshimagecachecollector.cpp b/src/plugins/qmldesigner/designercore/imagecache/meshimagecachecollector.cpp new file mode 100644 index 00000000000..7602ee7a119 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/meshimagecachecollector.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "meshimagecachecollector.h" +#include "imagecacheconnectionmanager.h" + +#include +#include +#include + +#include + +namespace QmlDesigner { + +MeshImageCacheCollector::MeshImageCacheCollector( + ImageCacheConnectionManager &connectionManager, + QSize captureImageMinimumSize, + QSize captureImageMaximumSize, + ImageCacheCollectorNullImageHandling nullImageHandling) + : m_imageCacheCollector(connectionManager, + captureImageMinimumSize, + captureImageMaximumSize, + nullImageHandling) +{} + +MeshImageCacheCollector::~MeshImageCacheCollector() = default; + +void MeshImageCacheCollector::start(Utils::SmallStringView name, + Utils::SmallStringView state, + const ImageCache::AuxiliaryData &auxiliaryData, + CaptureCallback captureCallback, + AbortCallback abortCallback) +{ + QTemporaryFile file(QDir::tempPath() + "/mesh-XXXXXX.qml"); + if (file.open()) { + QString qtQuickVersion; + QString qtQuick3DVersion; + QtSupport::QtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(target()->kit()); + if (qtVersion && qtVersion->qtVersion() < QtSupport::QtVersionNumber(6, 0, 0)) { + qtQuickVersion = "2.15"; + qtQuick3DVersion = "1.15"; + } + + QString content{ + R"(import QtQuick %1 + import QtQuick3D %2 + Node { + Model { + source: "%3" + DefaultMaterial { id: defaultMaterial; diffuseColor: "#ff999999" } + materials: [ defaultMaterial ] + } + })"}; + + content = content.arg(qtQuickVersion, qtQuick3DVersion, QString(name)); + + file.write(content.toUtf8()); + file.close(); + } + + Utils::PathString path{file.fileName()}; + + m_imageCacheCollector.start(path, state, auxiliaryData, captureCallback, abortCallback); +} + +std::pair MeshImageCacheCollector::createImage(Utils::SmallStringView, + Utils::SmallStringView, + const ImageCache::AuxiliaryData &) +{ + return {}; +} + +QIcon MeshImageCacheCollector::createIcon(Utils::SmallStringView, + Utils::SmallStringView, + const ImageCache::AuxiliaryData &) +{ + return {}; +} + +void MeshImageCacheCollector::setTarget(ProjectExplorer::Target *target) +{ + m_imageCacheCollector.setTarget(target); +} + +ProjectExplorer::Target *MeshImageCacheCollector::target() const +{ + return m_imageCacheCollector.target(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/meshimagecachecollector.h b/src/plugins/qmldesigner/designercore/imagecache/meshimagecachecollector.h new file mode 100644 index 00000000000..c2cc63bfd9e --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/meshimagecachecollector.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "imagecachecollectorinterface.h" +#include "imagecachecollector.h" + +namespace ProjectExplorer { +class Target; +} + +namespace QmlDesigner { + +class ImageCacheConnectionManager; + +class MeshImageCacheCollector final : public ImageCacheCollectorInterface +{ +public: + MeshImageCacheCollector(ImageCacheConnectionManager &connectionManager, + QSize captureImageMinimumSize, + QSize captureImageMaximumSize, + ImageCacheCollectorNullImageHandling nullImageHandling = {}); + + ~MeshImageCacheCollector(); + + void start(Utils::SmallStringView filePath, + Utils::SmallStringView state, + const ImageCache::AuxiliaryData &auxiliaryData, + CaptureCallback captureCallback, + AbortCallback abortCallback) override; + + std::pair createImage(Utils::SmallStringView filePath, + Utils::SmallStringView state, + const ImageCache::AuxiliaryData &auxiliaryData) override; + + QIcon createIcon(Utils::SmallStringView filePath, + Utils::SmallStringView state, + const ImageCache::AuxiliaryData &auxiliaryData) override; + + void setTarget(ProjectExplorer::Target *target); + ProjectExplorer::Target *target() const; + +private: + ImageCacheCollector m_imageCacheCollector; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/smallimagecacheprovider.cpp b/src/plugins/qmldesigner/designercore/imagecache/smallimagecacheprovider.cpp new file mode 100644 index 00000000000..36bd55c2fe6 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/smallimagecacheprovider.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "smallimagecacheprovider.h" + +#include + +#include + +namespace QmlDesigner { + +QQuickTextureFactory *ImageResponse::textureFactory() const +{ + return QQuickTextureFactory::textureFactoryForImage(m_image); +} + +void ImageResponse::setImage(const QImage &image) +{ + m_image = image; + + emit finished(); +} + +void ImageResponse::abort() +{ + emit finished(); +} + +QQuickImageResponse *SmallImageCacheProvider::requestImageResponse(const QString &id, const QSize &) +{ + auto response = std::make_unique(m_defaultImage); + + m_cache.requestSmallImage( + id, + [response = QPointer(response.get())](const QImage &image) { + QMetaObject::invokeMethod( + response, + [response, image] { + if (response) + response->setImage(image); + }, + Qt::QueuedConnection); + }, + [response = QPointer(response.get())]( + ImageCache::AbortReason abortReason) { + QMetaObject::invokeMethod( + response, + [response, abortReason] { + switch (abortReason) { + case ImageCache::AbortReason::Failed: + if (response) + response->abort(); + break; + case ImageCache::AbortReason::Abort: + response->cancel(); + break; + } + }, + Qt::QueuedConnection); + }); + + return response.release(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/smallimagecacheprovider.h b/src/plugins/qmldesigner/designercore/imagecache/smallimagecacheprovider.h new file mode 100644 index 00000000000..05674143e67 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/smallimagecacheprovider.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +namespace QmlDesigner { + +class AsynchronousImageCache; + +class ImageResponse : public QQuickImageResponse +{ +public: + ImageResponse(const QImage &defaultImage) + : m_image(defaultImage) + {} + + QQuickTextureFactory *textureFactory() const override; + + void setImage(const QImage &image); + + void abort(); + +private: + QImage m_image; +}; + +class SmallImageCacheProvider : public QQuickAsyncImageProvider +{ +public: + SmallImageCacheProvider(AsynchronousImageCache &imageCache, const QImage &defaultImage = {}) + : m_cache{imageCache} + , m_defaultImage(defaultImage) + {} + + QQuickImageResponse *requestImageResponse(const QString &id, + const QSize &requestedSize) override; + +private: + AsynchronousImageCache &m_cache; + QImage m_defaultImage; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/timestampprovider.cpp b/src/plugins/qmldesigner/designercore/imagecache/timestampprovider.cpp index 99573f175fb..67ccc7b75c0 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/timestampprovider.cpp +++ b/src/plugins/qmldesigner/designercore/imagecache/timestampprovider.cpp @@ -28,11 +28,17 @@ #include #include +#include + namespace QmlDesigner { Sqlite::TimeStamp TimeStampProvider::timeStamp(Utils::SmallStringView name) const { - return QFileInfo{QString{name}}.lastModified().toSecsSinceEpoch(); + QFileInfo info{QString{name}}; + if (info.exists()) + return info.lastModified().toSecsSinceEpoch(); + + return {std::numeric_limits::max()}; } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/include/viewmanager.h b/src/plugins/qmldesigner/designercore/include/viewmanager.h index dd77c07f459..1be785655a2 100644 --- a/src/plugins/qmldesigner/designercore/include/viewmanager.h +++ b/src/plugins/qmldesigner/designercore/include/viewmanager.h @@ -51,7 +51,8 @@ class ViewManagerData; class QMLDESIGNERCORE_EXPORT ViewManager { public: - ViewManager(class AsynchronousImageCache &imageCache); + ViewManager(class AsynchronousImageCache &imageCache, + class AsynchronousImageCache &meshImageCache); ~ViewManager(); void attachRewriterView(); diff --git a/src/plugins/qmldesigner/designercore/model/viewmanager.cpp b/src/plugins/qmldesigner/designercore/model/viewmanager.cpp index 8e96507cd53..2896c1571db 100644 --- a/src/plugins/qmldesigner/designercore/model/viewmanager.cpp +++ b/src/plugins/qmldesigner/designercore/model/viewmanager.cpp @@ -62,8 +62,9 @@ static Q_LOGGING_CATEGORY(viewBenchmark, "qtc.viewmanager.attach", QtWarningMsg) class ViewManagerData { public: - ViewManagerData(AsynchronousImageCache &imageCache) + ViewManagerData(AsynchronousImageCache &imageCache, AsynchronousImageCache &meshImageCache) : itemLibraryView(imageCache) + , propertyEditorView(meshImageCache) {} InteractiveConnectionManager connectionManager; @@ -94,8 +95,8 @@ static CrumbleBar *crumbleBar() { return QmlDesignerPlugin::instance()->mainWidget()->crumbleBar(); } -ViewManager::ViewManager(AsynchronousImageCache &imageCache) - : d(std::make_unique(imageCache)) +ViewManager::ViewManager(AsynchronousImageCache &imageCache, AsynchronousImageCache &meshImageCache) + : d(std::make_unique(imageCache, meshImageCache)) { d->formEditorView.setGotoErrorCallback([this](int line, int column) { d->textEditorView.gotoCursorPosition(line, column); diff --git a/src/plugins/qmldesigner/qmldesignercore.cmake b/src/plugins/qmldesigner/qmldesignercore.cmake index 83870a36fee..9a2b0b6f3e6 100644 --- a/src/plugins/qmldesigner/qmldesignercore.cmake +++ b/src/plugins/qmldesigner/qmldesignercore.cmake @@ -123,6 +123,8 @@ function(extend_with_qmldesigner_core target_name) imagecache/imagecachegeneratorinterface.h imagecache/imagecachestorage.h imagecache/imagecachestorageinterface.h + imagecache/meshimagecachecollector.cpp + imagecache/meshimagecachecollector.h imagecache/synchronousimagecache.cpp imagecache/timestampprovider.cpp imagecache/timestampprovider.h diff --git a/src/plugins/qmldesigner/qmldesignerplugin.cpp b/src/plugins/qmldesigner/qmldesignerplugin.cpp index e6f40d14fc6..5ab50135eb2 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.cpp +++ b/src/plugins/qmldesigner/qmldesignerplugin.cpp @@ -137,7 +137,8 @@ class QmlDesignerPluginPrivate { public: QmlDesignerProjectManager projectManager; - ViewManager viewManager{projectManager.asynchronousImageCache()}; + ViewManager viewManager{projectManager.asynchronousImageCache(), + projectManager.asynchronousMeshImageCache()}; DocumentManager documentManager; ShortCutManager shortCutManager; SettingsPage settingsPage; diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index ed113d4fe57..5266a146a3d 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -442,6 +442,10 @@ Project { "imagecache/imagecachegenerator.h", "imagecache/imagecachestorageinterface.h", "imagecache/imagecachestorage.h", + "imagecache/meshimagecachecollector.cpp", + "imagecache/meshimagecachecollector.h", + "imagecache/smallimagecacheprovider.cpp", + "imagecache/smallimagecacheprovider.h", "imagecache/synchronousimagecache.cpp", "imagecache/timestampproviderinterface.h", "imagecache/timestampprovider.h", @@ -737,6 +741,8 @@ Project { "propertyeditor/gradientpresetlistmodel.h", "propertyeditor/propertyeditorcontextobject.cpp", "propertyeditor/propertyeditorcontextobject.h", + "propertyeditor/propertyeditorimageprovider.cpp", + "propertyeditor/propertyeditorimageprovider.h", "propertyeditor/propertyeditortransaction.cpp", "propertyeditor/propertyeditortransaction.h", "propertyeditor/propertyeditorvalue.cpp", diff --git a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp b/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp index 43070221bcf..90e54ba65bb 100644 --- a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp +++ b/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp @@ -51,7 +51,8 @@ #include #include #include -#include +#include +#include #include @@ -79,7 +80,7 @@ QString defaultImagePath() return qobject_cast<::QmlProjectManager::QmlBuildSystem *>(target->buildSystem()); } -class TimeStampProvider : public TimeStampProviderInterface +class PreviewTimeStampProvider : public TimeStampProviderInterface { public: Sqlite::TimeStamp timeStamp(Utils::SmallStringView) const override @@ -102,15 +103,18 @@ class QmlDesignerProjectManager::ImageCacheData { public: Sqlite::Database database{Utils::PathString{ - Core::ICore::cacheResourcePath("imagecache-v2.db").toString()}, + Core::ICore::cacheResourcePath("imagecache-v2.db").toString()}, Sqlite::JournalMode::Wal, Sqlite::LockingMode::Normal}; ImageCacheStorage storage{database}; ImageCacheConnectionManager connectionManager; - ImageCacheCollector collector{connectionManager, QSize{300, 300}, QSize{600, 600}}; - ImageCacheGenerator generator{collector, storage}; + MeshImageCacheCollector meshImageCollector{connectionManager, QSize{300, 300}, QSize{600, 600}}; + ImageCacheGenerator meshGenerator{meshImageCollector, storage}; + ImageCacheCollector nodeInstanceCollector{connectionManager, QSize{300, 300}, QSize{600, 600}}; + ImageCacheGenerator nodeInstanceGenerator{nodeInstanceCollector, storage}; TimeStampProvider timeStampProvider; - AsynchronousImageCache asynchronousImageCache{storage, generator, timeStampProvider}; + AsynchronousImageCache asynchronousImageCache{storage, nodeInstanceGenerator, timeStampProvider}; + AsynchronousImageCache asynchronousMeshImageCache{storage, meshGenerator, timeStampProvider}; }; class QmlDesignerProjectManager::PreviewImageCacheData @@ -135,7 +139,7 @@ public: QSize{300, 300}, QSize{1000, 1000}, ImageCacheCollectorNullImageHandling::DontCaptureNullImage}; - TimeStampProvider timeStampProvider; + PreviewTimeStampProvider timeStampProvider; AsynchronousImageFactory factory; ::ProjectExplorer::Target *activeTarget = nullptr; }; @@ -180,6 +184,11 @@ AsynchronousImageCache &QmlDesignerProjectManager::asynchronousImageCache() return imageCacheData()->asynchronousImageCache; } +AsynchronousImageCache &QmlDesignerProjectManager::asynchronousMeshImageCache() +{ + return imageCacheData()->asynchronousMeshImageCache; +} + void QmlDesignerProjectManager::editorOpened(::Core::IEditor *) {} void QmlDesignerProjectManager::currentEditorChanged(::Core::IEditor *) @@ -218,17 +227,21 @@ QmlDesignerProjectManager::ImageCacheData *QmlDesignerProjectManager::imageCache m_imageCacheData = std::make_unique(); auto setTargetInImageCache = [imageCacheData = m_imageCacheData.get()](ProjectExplorer::Target *target) { - if (target == imageCacheData->collector.target()) + if (target == imageCacheData->nodeInstanceCollector.target()) return; if (target) imageCacheData->asynchronousImageCache.clean(); - imageCacheData->collector.setTarget(target); + // TODO wrap in function in image cache data + imageCacheData->meshImageCollector.setTarget(target); + imageCacheData->nodeInstanceCollector.setTarget(target); }; if (auto project = ProjectExplorer::SessionManager::startupProject(); project) { - m_imageCacheData->collector.setTarget(project->activeTarget()); + // TODO wrap in function in image cache data + m_imageCacheData->meshImageCollector.setTarget(project->activeTarget()); + m_imageCacheData->nodeInstanceCollector.setTarget(project->activeTarget()); QObject::connect(project, &ProjectExplorer::Project::activeTargetChanged, this, diff --git a/src/plugins/qmldesigner/qmldesignerprojectmanager.h b/src/plugins/qmldesigner/qmldesignerprojectmanager.h index 6b94fa9e6f4..68cd809be3d 100644 --- a/src/plugins/qmldesigner/qmldesignerprojectmanager.h +++ b/src/plugins/qmldesigner/qmldesignerprojectmanager.h @@ -59,6 +59,7 @@ public: void registerPreviewImageProvider(QQmlEngine *engine) const; class AsynchronousImageCache &asynchronousImageCache(); + class AsynchronousImageCache &asynchronousMeshImageCache(); private: void editorOpened(::Core::IEditor *editor); From 519f6fbda11cd6849e2ddbc804e20af2d2030012 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Fri, 3 Jun 2022 13:40:39 +0200 Subject: [PATCH 10/53] QmlDesigner: Add drag'n'drop to PropertyEditorView * Implement drag and drop for UrlChooser and FontComboBox * Change the style of controls accepting drag payload and drag hovering. Utilize states for those styles. * Fix aspect ratio of drag pixmap * Fix issue that causes drag to continue after pressing the escape key Change-Id: I5cf67175abe936e60e8af00fa8c2f7a2dec355b3 Reviewed-by: Thomas Hartmann Reviewed-by: Reviewed-by: Mahmoud Badri --- .../imports/HelperWidgets/FontComboBox.qml | 34 ++++++++++++-- .../imports/HelperWidgets/UrlChooser.qml | 27 +++++++++++- .../imports/StudioControls/CheckIndicator.qml | 13 +++++- .../imports/StudioControls/ComboBox.qml | 44 ++++++++++++------- .../imports/StudioControls/ComboBoxInput.qml | 17 ++++--- .../imports/StudioControls/FilterComboBox.qml | 32 +++++++++++++- .../assetslibraryiconprovider.cpp | 2 +- .../assetslibrary/assetslibrarywidget.cpp | 4 -- .../itemlibrary/itemlibrarywidget.cpp | 4 -- .../propertyeditorcontextobject.cpp | 13 ++++++ .../propertyeditorcontextobject.h | 8 ++++ .../propertyeditor/propertyeditorvalue.cpp | 2 + .../propertyeditor/propertyeditorview.cpp | 27 +++++++++--- .../propertyeditor/propertyeditorview.h | 3 ++ .../qmldesigner/designercore/model/model.cpp | 4 +- 15 files changed, 191 insertions(+), 43 deletions(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/FontComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/FontComboBox.qml index 6d9f56b178c..8c05fa2f878 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/FontComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/FontComboBox.qml @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -36,6 +36,7 @@ StudioControls.ComboBox { property string fontFilter: "*.ttf *.otf" property bool showExtendedFunctionButton: true + hasActiveDrag: activeDragSuffix !== "" && root.fontFilter.includes(activeDragSuffix) labelColor: colorLogic.textColor editable: true @@ -47,16 +48,43 @@ StudioControls.ComboBox { filter: root.fontFilter } + DropArea { + id: dropArea + + anchors.fill: parent + + property string assetPath: "" + + onEntered: function(drag) { + dropArea.assetPath = drag.getDataAsString(drag.keys[0]).split(",")[0] + drag.accepted = root.hasActiveDrag + root.hasActiveHoverDrag = drag.accepted + } + + onExited: root.hasActiveHoverDrag = false + + onDropped: function(drop) { + drop.accepted = root.hasActiveHoverDrag + var fontLoader = root.createFontLoader("file:///" + dropArea.assetPath) + root.backendValue.value = fontLoader.name + root.currentIndex = root.find(root.backendValue.value) + root.hasActiveHoverDrag = false + root.backendValue.commitDrop(dropArea.assetPath) + } + } + function createFontLoader(fontUrl) { return Qt.createQmlObject('import QtQuick 2.0; FontLoader { source: "' + fontUrl + '"; }', root, "dynamicFontLoader") } function setupModel() { - var familyNames = ["Arial", "Times New Roman", "Courier", "Verdana", "Tahoma"] // default fonts + // default fonts + var familyNames = ["Arial", "Times New Roman", "Courier", "Verdana", "Tahoma"] for (var i = 0; i < fileModel.model.length; ++i) { // add custom fonts - var fontLoader = createFontLoader(fileModel.docPath + "/" + fileModel.model[i].relativeFilePath) + var fontLoader = root.createFontLoader(fileModel.docPath + "/" + + fileModel.model[i].relativeFilePath) familyNames.push(fontLoader.name) } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml index ec246f56f28..ecb4936bd7d 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -63,6 +63,7 @@ Row { property ListModel listModel: ListModel {} + hasActiveDrag: activeDragSuffix !== "" && root.filter.includes(activeDragSuffix) implicitWidth: StudioTheme.Values.singleControlColumnWidth + StudioTheme.Values.actionIndicatorWidth width: implicitWidth @@ -72,6 +73,30 @@ Row { // when the combobox is closed by focusing on some other control. property int hoverIndex: -1 + DropArea { + id: dropArea + + anchors.fill: parent + + property string assetPath: "" + + onEntered: function(drag) { + dropArea.assetPath = drag.getDataAsString(drag.keys[0]).split(",")[0] + drag.accepted = comboBox.hasActiveDrag + comboBox.hasActiveHoverDrag = drag.accepted + } + + onExited: comboBox.hasActiveHoverDrag = false + + onDropped: function(drop) { + drop.accepted = comboBox.hasActiveHoverDrag + comboBox.editText = dropArea.assetPath + comboBox.accepted() + comboBox.hasActiveHoverDrag = false + root.backendValue.commitDrop(dropArea.assetPath) + } + } + ToolTip { id: toolTip visible: comboBox.hover && toolTip.text !== "" diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/CheckIndicator.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/CheckIndicator.qml index 2d3bb74c3f7..5af0f5fe6e9 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/CheckIndicator.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/CheckIndicator.qml @@ -37,6 +37,9 @@ Rectangle { property bool pressed: checkIndicatorMouseArea.containsPress property bool checked: false + property bool hasActiveDrag: myControl.hasActiveDrag ?? false + property bool hasActiveHoverDrag: myControl.hasActiveHoverDrag ?? false + color: StudioTheme.Values.themeControlBackground border.width: 0 @@ -79,12 +82,20 @@ Rectangle { name: "default" when: myControl.enabled && checkIndicator.enabled && !myControl.edit && !checkIndicator.hover && !myControl.hover && !myControl.drag - && !checkIndicator.checked + && !checkIndicator.checked && !checkIndicator.hasActiveDrag PropertyChanges { target: checkIndicator color: StudioTheme.Values.themeControlBackground } }, + State { + name: "dragHover" + when: myControl.enabled && checkIndicator.hasActiveHoverDrag + PropertyChanges { + target: checkIndicator + color: StudioTheme.Values.themeControlBackgroundInteraction + } + }, State { name: "globalHover" when: myControl.enabled && checkIndicator.enabled && !myControl.drag diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBox.qml index 70cbdf000ec..f57a1c404e3 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBox.qml @@ -50,9 +50,6 @@ T.ComboBox { property alias textInput: comboBoxInput - property int borderWidth: myComboBox.hasActiveHoverDrag ? StudioTheme.Values.borderHover - : StudioTheme.Values.border - signal compressedActivated(int index, int reason) enum ActivatedReason { EditingFinished, Other } @@ -61,7 +58,7 @@ T.ComboBox { height: StudioTheme.Values.defaultControlHeight leftPadding: actionIndicator.width - rightPadding: popupIndicator.width + myComboBox.borderWidth + rightPadding: popupIndicator.width + StudioTheme.Values.border font.pixelSize: StudioTheme.Values.myFontSize wheelEnabled: false @@ -91,7 +88,6 @@ T.ComboBox { myControl: myComboBox text: myComboBox.editText - borderWidth: myComboBox.borderWidth onEditingFinished: { comboBoxInput.deselect() @@ -113,16 +109,16 @@ T.ComboBox { myControl: myComboBox myPopup: myComboBox.popup x: comboBoxInput.x + comboBoxInput.width - y: myComboBox.borderWidth - width: StudioTheme.Values.checkIndicatorWidth - myComboBox.borderWidth - height: StudioTheme.Values.checkIndicatorHeight - myComboBox.borderWidth * 2 + y: StudioTheme.Values.border + width: StudioTheme.Values.checkIndicatorWidth - StudioTheme.Values.border + height: StudioTheme.Values.checkIndicatorHeight - StudioTheme.Values.border * 2 } background: Rectangle { id: comboBoxBackground color: StudioTheme.Values.themeControlBackground border.color: StudioTheme.Values.themeControlOutline - border.width: myComboBox.borderWidth + border.width: StudioTheme.Values.border x: actionIndicator.width width: myComboBox.width - actionIndicator.width height: myComboBox.height @@ -149,7 +145,7 @@ T.ComboBox { width: comboBoxPopup.width - comboBoxPopup.leftPadding - comboBoxPopup.rightPadding - (comboBoxPopupScrollBar.visible ? comboBoxPopupScrollBar.contentItem.implicitWidth + 2 : 0) // TODO Magic number - height: StudioTheme.Values.height - 2 * myComboBox.borderWidth + height: StudioTheme.Values.height - 2 * StudioTheme.Values.border padding: 0 enabled: model.enabled === undefined ? true : model.enabled @@ -203,9 +199,9 @@ T.ComboBox { popup: T.Popup { id: comboBoxPopup - x: actionIndicator.width + myComboBox.borderWidth + x: actionIndicator.width + StudioTheme.Values.border y: myComboBox.height - width: myComboBox.width - actionIndicator.width - myComboBox.borderWidth * 2 + width: myComboBox.width - actionIndicator.width - StudioTheme.Values.border * 2 // TODO Setting the height on the popup solved the problem with the popup of height 0, // but it has the problem that it sometimes extend over the border of the actual window // and is then cut off. @@ -213,7 +209,7 @@ T.ComboBox { + comboBoxPopup.bottomPadding, myComboBox.Window.height - topMargin - bottomMargin, StudioTheme.Values.maxComboBoxPopupHeight) - padding: myComboBox.borderWidth + padding: StudioTheme.Values.border margins: 0 // If not defined margin will be -1 closePolicy: T.Popup.CloseOnPressOutside | T.Popup.CloseOnPressOutsideParent | T.Popup.CloseOnEscape | T.Popup.CloseOnReleaseOutside @@ -245,7 +241,7 @@ T.ComboBox { State { name: "default" when: myComboBox.enabled && !myComboBox.hover && !myComboBox.edit && !myComboBox.open - && !myComboBox.activeFocus + && !myComboBox.activeFocus && !myComboBox.hasActiveDrag PropertyChanges { target: myComboBox wheelEnabled: false @@ -257,9 +253,23 @@ T.ComboBox { PropertyChanges { target: comboBoxBackground color: StudioTheme.Values.themeControlBackground - border.color: myComboBox.hasActiveDrag ? StudioTheme.Values.themeInteraction - : StudioTheme.Values.themeControlOutline - border.width: myComboBox.borderWidth + } + }, + State { + name: "acceptsDrag" + when: myComboBox.enabled && myComboBox.hasActiveDrag && !myComboBox.hasActiveHoverDrag + PropertyChanges { + target: comboBoxBackground + border.color: StudioTheme.Values.themeControlOutlineInteraction + } + }, + State { + name: "dragHover" + when: myComboBox.enabled && myComboBox.hasActiveHoverDrag + PropertyChanges { + target: comboBoxBackground + color: StudioTheme.Values.themeControlBackgroundInteraction + border.color: StudioTheme.Values.themeControlOutlineInteraction } }, // This state is intended for ComboBoxes which aren't editable, but have focus e.g. via diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBoxInput.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBoxInput.qml index c6b91dc1ad0..e2596876987 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBoxInput.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBoxInput.qml @@ -34,7 +34,6 @@ TextInput { property bool edit: textInput.activeFocus property bool hover: mouseArea.containsMouse && textInput.enabled - property int borderWidth: StudioTheme.Values.border z: 2 font: myControl.font @@ -56,11 +55,11 @@ TextInput { Rectangle { id: textInputBackground - x: textInput.borderWidth - y: textInput.borderWidth + x: StudioTheme.Values.border + y: StudioTheme.Values.border z: -1 width: textInput.width - height: StudioTheme.Values.height - textInput.borderWidth * 2 + height: StudioTheme.Values.height - StudioTheme.Values.border * 2 color: StudioTheme.Values.themeControlBackground border.width: 0 } @@ -94,7 +93,7 @@ TextInput { State { name: "default" when: myControl.enabled && !textInput.edit && !textInput.hover && !myControl.hover - && !myControl.open + && !myControl.open && !myControl.hasActiveDrag PropertyChanges { target: textInputBackground color: StudioTheme.Values.themeControlBackground @@ -105,6 +104,14 @@ TextInput { acceptedButtons: Qt.LeftButton } }, + State { + name: "dragHover" + when: myControl.enabled && myControl.hasActiveHoverDrag + PropertyChanges { + target: textInputBackground + color: StudioTheme.Values.themeControlBackgroundInteraction + } + }, State { name: "globalHover" when: myControl.hover && !textInput.hover && !textInput.edit && !myControl.open diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/FilterComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/FilterComboBox.qml index 30142652ab3..c41fe060e50 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/FilterComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/FilterComboBox.qml @@ -87,6 +87,9 @@ Item { property alias popupScrollBar: popupScrollBar property alias popupMouseArea: popupMouseArea + property bool hasActiveDrag: false // an item that can be dropped here is being dragged + property bool hasActiveHoverDrag: false // an item that can be dropped her is being hovered on top + width: StudioTheme.Values.defaultControlWidth height: StudioTheme.Values.defaultControlHeight implicitHeight: StudioTheme.Values.defaultControlHeight @@ -468,9 +471,11 @@ Item { State { name: "default" when: root.enabled && !textInput.edit && !root.hover && !root.open + && !root.hasActiveDrag PropertyChanges { target: textInputBackground color: StudioTheme.Values.themeControlBackground + border.color: StudioTheme.Values.themeControlOutline } PropertyChanges { target: textInputMouseArea @@ -478,6 +483,23 @@ Item { acceptedButtons: Qt.LeftButton } }, + State { + name: "acceptsDrag" + when: root.enabled && root.hasActiveDrag && !root.hasActiveHoverDrag + PropertyChanges { + target: textInputBackground + border.color: StudioTheme.Values.themeInteraction + } + }, + State { + name: "dragHover" + when: root.enabled && root.hasActiveHoverDrag + PropertyChanges { + target: textInputBackground + color: StudioTheme.Values.themeControlBackgroundInteraction + border.color: StudioTheme.Values.themeInteraction + } + }, State { name: "globalHover" when: root.hover && !textInput.hover && !textInput.edit && !root.open @@ -585,12 +607,20 @@ Item { name: "default" when: root.enabled && checkIndicator.enabled && !root.edit && !checkIndicator.hover && !root.hover - && !checkIndicator.checked + && !checkIndicator.checked && !root.hasActiveHoverDrag PropertyChanges { target: checkIndicator color: StudioTheme.Values.themeControlBackground } }, + State { + name: "dragHover" + when: root.enabled && root.hasActiveHoverDrag + PropertyChanges { + target: checkIndicator + color: StudioTheme.Values.themeControlBackgroundInteraction + } + }, State { name: "globalHover" when: root.enabled && checkIndicator.enabled diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp index 9d11d264008..943693f2232 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp @@ -75,7 +75,7 @@ QPixmap AssetsLibraryIconProvider::requestPixmap(const QString &id, QSize *size, pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/assets_default.png"); if (requestedSize.isValid()) - return pixmap.scaled(requestedSize); + return pixmap.scaled(requestedSize, Qt::KeepAspectRatio); return pixmap; } diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp index 7cf91158d65..632574fb0fc 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp @@ -91,10 +91,6 @@ bool AssetsLibraryWidget::eventFilter(QObject *obj, QEvent *event) m_assetsToDrag.clear(); } } - } else if (event->type() == QMouseEvent::MouseButtonRelease) { - m_assetsToDrag.clear(); - if (m_model) - m_model->endDrag(); } return QObject::eventFilter(obj, event); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp index e6f2b2825e5..109078e1561 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp @@ -126,10 +126,6 @@ bool ItemLibraryWidget::eventFilter(QObject *obj, QEvent *event) m_itemToDrag = {}; } } - } else if (event->type() == QMouseEvent::MouseButtonRelease) { - m_itemToDrag = {}; - if (model) - model->endDrag(); } return QObject::eventFilter(obj, event); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp index 3a9a138b2a3..8ba1325efaf 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp @@ -322,6 +322,19 @@ void PropertyEditorContextObject::insertKeyframe(const QString &propertyName) }); } +QString PropertyEditorContextObject::activeDragSuffix() const +{ + return m_activeDragSuffix; +} + +void PropertyEditorContextObject::setActiveDragSuffix(const QString &suffix) +{ + if (m_activeDragSuffix != suffix) { + m_activeDragSuffix = suffix; + emit activeDragSuffixChanged(); + } +} + int PropertyEditorContextObject::majorVersion() const { return m_majorVersion; diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h index 6a3e410c8a1..f5a2224c8b7 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h @@ -56,6 +56,8 @@ class PropertyEditorContextObject : public QObject Q_PROPERTY(int majorQtQuickVersion READ majorQtQuickVersion WRITE setMajorQtQuickVersion NOTIFY majorQtQuickVersionChanged) Q_PROPERTY(int minorQtQuickVersion READ minorQtQuickVersion WRITE setMinorQtQuickVersion NOTIFY minorQtQuickVersionChanged) + Q_PROPERTY(QString activeDragSuffix READ activeDragSuffix NOTIFY activeDragSuffixChanged) + Q_PROPERTY(bool hasAliasExport READ hasAliasExport NOTIFY hasAliasExportChanged) Q_PROPERTY(bool hasActiveTimeline READ hasActiveTimeline NOTIFY hasActiveTimelineChanged) @@ -102,6 +104,9 @@ public: Q_INVOKABLE bool isBlocked(const QString &propName) const; + QString activeDragSuffix() const; + void setActiveDragSuffix(const QString &suffix); + int majorVersion() const; int majorQtQuickVersion() const; int minorQtQuickVersion() const; @@ -134,6 +139,7 @@ signals: void specificQmlComponentChanged(); void hasAliasExportChanged(); void hasActiveTimelineChanged(); + void activeDragSuffixChanged(); public slots: @@ -182,6 +188,8 @@ private: bool m_aliasExport = false; bool m_setHasActiveTimeline = false; + + QString m_activeDragSuffix; }; class EasingCurveEditor : public QObject diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp index 1ef95365e13..867fd9bd65b 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp @@ -537,6 +537,8 @@ void PropertyEditorValue::commitDrop(const QString &path) // assign the texture to the property setExpressionWithEmit(texture.id()); } + + m_modelNode.view()->model()->endDrag(); } QStringList PropertyEditorValue::generateStringList(const QString &string) const diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index 586c4dec054..b516bba9c95 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -26,8 +26,8 @@ #include "propertyeditorview.h" #include "propertyeditorqmlbackend.h" -#include "propertyeditorvalue.h" #include "propertyeditortransaction.h" +#include "propertyeditorvalue.h" #include #include @@ -850,7 +850,26 @@ void PropertyEditorView::nodeReparented(const ModelNode &node, m_qmlBackEndForCurrentType->backendAnchorBinding().setup(QmlItemNode(m_selectedNode)); } -void PropertyEditorView::setValue(const QmlObjectNode &qmlObjectNode, const PropertyName &name, const QVariant &value) +void PropertyEditorView::dragStarted(QMimeData *mimeData) +{ + if (!mimeData->hasFormat(Constants::MIME_TYPE_ASSETS)) + return; + + const QString assetPath = QString::fromUtf8(mimeData->data(Constants::MIME_TYPE_ASSETS)) + .split(',')[0]; + const QString suffix = "*." + assetPath.split('.').last().toLower(); + + m_qmlBackEndForCurrentType->contextObject()->setActiveDragSuffix(suffix); +} + +void PropertyEditorView::dragEnded() +{ + m_qmlBackEndForCurrentType->contextObject()->setActiveDragSuffix(""); +} + +void PropertyEditorView::setValue(const QmlObjectNode &qmlObjectNode, + const PropertyName &name, + const QVariant &value) { m_locked = true; m_qmlBackEndForCurrentType->setValue(qmlObjectNode, name, value); @@ -869,6 +888,4 @@ void PropertyEditorView::reloadQml() resetView(); } - -} //QmlDesigner - +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h index 3bbe5020506..09d6dc7f419 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h @@ -87,6 +87,9 @@ public: const NodeAbstractProperty &oldPropertyParent, AbstractView::PropertyChangeFlags propertyChange) override; + void dragStarted(QMimeData *mimeData) override; + void dragEnded() override; + void changeValue(const QString &name); void changeExpression(const QString &name); void exportPropertyAsAlias(const QString &name); diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index 4e38fb3cfb3..47ce515f9e2 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -1517,7 +1517,9 @@ void Model::startDrag(QMimeData *mimeData, const QPixmap &icon) auto drag = new QDrag(this); drag->setPixmap(icon); drag->setMimeData(mimeData); - drag->exec(); + if (drag->exec() == Qt::IgnoreAction) + endDrag(); + drag->deleteLater(); } From a5d501d22e78948c2bf300b5e6bf01ecd44352a0 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Thu, 9 Jun 2022 09:30:24 +0200 Subject: [PATCH 11/53] QmlDesigner: Fix template Add missing '}' Task-number: QDS-7125 Change-Id: I7ecf80d681de0400368e28b98179488db560381e Reviewed-by: Thomas Hartmann --- .../studio_templates/projects/application-3d/Screen01.ui.qml.tpl | 1 + 1 file changed, 1 insertion(+) diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/Screen01.ui.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/Screen01.ui.qml.tpl index 24566ef2cc2..843185a029f 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/Screen01.ui.qml.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/Screen01.ui.qml.tpl @@ -56,6 +56,7 @@ Rectangle { objectName: "Default Material" diffuseColor: "#4aee45" } + } Text { text: qsTr("Hello %{ProjectName}") From ab635dd4b220b88c96dd240ddc03670b7ddf8175 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Thu, 9 Jun 2022 12:33:48 +0200 Subject: [PATCH 12/53] QmlDesigner: Do license check only once We should only make the license check once. An evaluationLicense is handled as enterprise license. Change-Id: Ib4b53795a7d735c10b5238f1e7b76346a9bcc8e1 Reviewed-by: Reviewed-by: Tim Jenssen --- src/plugins/qmldesigner/dynamiclicensecheck.h | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/dynamiclicensecheck.h b/src/plugins/qmldesigner/dynamiclicensecheck.h index fb1e3661b85..8ddcf55a15e 100644 --- a/src/plugins/qmldesigner/dynamiclicensecheck.h +++ b/src/plugins/qmldesigner/dynamiclicensecheck.h @@ -37,6 +37,7 @@ namespace QmlDesigner { enum FoundLicense { + noLicense, community, professional, enterprise @@ -58,12 +59,28 @@ ExtensionSystem::IPlugin *licenseCheckerPlugin() FoundLicense checkLicense() { + static FoundLicense license = noLicense; + + if (license != noLicense) + return license; + if (auto plugin = Internal::licenseCheckerPlugin()) { bool retVal = false; + bool success = QMetaObject::invokeMethod(plugin, - "qdsEnterpriseLicense", + "evaluationLicense", Qt::DirectConnection, Q_RETURN_ARG(bool, retVal)); + + if (success && retVal) + return enterprise; + + retVal = false; + + success = QMetaObject::invokeMethod(plugin, + "qdsEnterpriseLicense", + Qt::DirectConnection, + Q_RETURN_ARG(bool, retVal)); if (success && retVal) return enterprise; else From 8ca39444f4cadff3ae543877008cd9f6443380db Mon Sep 17 00:00:00 2001 From: Mats Honkamaa Date: Mon, 6 Jun 2022 10:36:41 +0300 Subject: [PATCH 13/53] Doc: Add documentation for Material Editor and Browser - Add Material Editor and Browser docs - Reorganize and edit current materials docs Task-number: QDS-6991 Change-Id: Ibea128dc48c0c2e167abf36e7700d4516673b45b Reviewed-by: Mahmoud Badri --- .../images/icons/apply-material.png | Bin 0 -> 779 bytes .../images/material-editor-browser.webp | Bin 0 -> 49886 bytes .../images/materials-remove-material.png | Bin 0 -> 15142 bytes .../images/navigator-material-texture.png | Bin 0 -> 3820 bytes .../studio-qtquick-3d-default-material.webp | Bin 0 -> 13236 bytes .../images/studio-qtquick-3d-material.webp | Bin 0 -> 35562 bytes .../src/qtdesignstudio-toc.qdoc | 1 + .../qtdesignstudio-3d-editor.qdoc | 2 +- .../qtdesignstudio-3d-materials-shaders.qdoc | 173 +----------- .../src/views/qtquick-components-view.qdoc | 2 +- .../src/views/studio-material-editor.qdoc | 266 ++++++++++++++++++ 11 files changed, 278 insertions(+), 166 deletions(-) create mode 100644 doc/qtdesignstudio/images/icons/apply-material.png create mode 100644 doc/qtdesignstudio/images/material-editor-browser.webp create mode 100644 doc/qtdesignstudio/images/materials-remove-material.png create mode 100644 doc/qtdesignstudio/images/navigator-material-texture.png create mode 100644 doc/qtdesignstudio/images/studio-qtquick-3d-default-material.webp create mode 100644 doc/qtdesignstudio/images/studio-qtquick-3d-material.webp create mode 100644 doc/qtdesignstudio/src/views/studio-material-editor.qdoc diff --git a/doc/qtdesignstudio/images/icons/apply-material.png b/doc/qtdesignstudio/images/icons/apply-material.png new file mode 100644 index 0000000000000000000000000000000000000000..d0b347470bc17f110ef71a28c7919fa27a2805e9 GIT binary patch literal 779 zcmeAS@N?(olHy`uVBq!ia0y~yV31~DV36WqVqjo6`D(**1_q`}o-U3d9>}2@4g>4d+g`)D|kryA2qjq+&pdJ z(g;6+6Q;A*?Q>iCI^OToqnQ~&?;DiDf}A4!yWnJ(v|Bg6>gt5KI#4DUrP6~T)!!L3R&ex5`GCR zThe))TlcC$MB(~s$>|@3c+dG&fA?Ta_DTEF#gT|8L97Y}~u0K{& zt|~s6`^!c=;q9bSWZwJ4~mGEF+%RgJDe#RnJm%sTwzNeyGulgFk>dLw*?2-Rw zcEs_nSJOVsy!vaUbHnZVhK_&jR%&fJv1sQ)A)On%uCL$7>4xSCwI-S{PkHxT=aHGV zw)X?QZ#?r=SFSmuEB29(-{-L8%%5VVe7rt634iRB_I`f1^OS|~UN>=}gN$o$z486~ z-sw;K*NN9Y=^l+2Jn``1&;E6>zy2(-@SVFM{Al5vCp_sB-k-brB(>>$;z7aM;_rVL zW2c-@t!dsB<$L|PkyccMlD59|*1hL6l=tqe5Gu1Y|Lm;oEi`4ycAMsds~1-Mo1x~p z?n~L({gxm1Ut2k)f45!kG$lz+YCpihz`)??>gTe~DWM4f^lEnt literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/images/material-editor-browser.webp b/doc/qtdesignstudio/images/material-editor-browser.webp new file mode 100644 index 0000000000000000000000000000000000000000..d3963b522e4730736f8433cf2375eb3ca0405977 GIT binary patch literal 49886 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

jPx+euynZ1j;dhuv>cX;ADv1wQ19oScV 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 OrG_&KFt9NMfcOBR3{X4( literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/images/materials-remove-material.png b/doc/qtdesignstudio/images/materials-remove-material.png new file mode 100644 index 0000000000000000000000000000000000000000..9ef0a91e5be01fef54067ad05e81d574e6db631f GIT binary patch literal 15142 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 h99YO9`QyQl|GjhFeoo@r^NNoTB literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/images/navigator-material-texture.png b/doc/qtdesignstudio/images/navigator-material-texture.png new file mode 100644 index 0000000000000000000000000000000000000000..4256e959c61e4c6ed9931c29df02aea9fada8429 GIT binary patch literal 3820 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 literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/images/studio-qtquick-3d-default-material.webp b/doc/qtdesignstudio/images/studio-qtquick-3d-default-material.webp new file mode 100644 index 0000000000000000000000000000000000000000..0306408df162c565a399044e149a961f5adb3b29 GIT binary patch literal 13236 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 H01zJlA=b2d literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/images/studio-qtquick-3d-material.webp b/doc/qtdesignstudio/images/studio-qtquick-3d-material.webp new file mode 100644 index 0000000000000000000000000000000000000000..1c5b1cecf28eb07e60d3528fbbe9f484672fb54e GIT binary patch literal 35562 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 literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc b/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc index f3a7a0d4f7d..8cba9a73076 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc +++ b/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc @@ -39,6 +39,7 @@ \list \li \l{Form Editor} \li \l{3D Editor} + \li \l{Material Editor and Browser} \li \l{Components} \li \l{Assets} \li \l{Navigator} diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc index dee9a01a9d9..1995c343b83 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc @@ -26,7 +26,7 @@ /*! \previouspage qtquick-form-editor.html \page studio-3d-editor.html - \nextpage quick-components-view.html + \nextpage studio-material-editor.html \title 3D Editor diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-materials-shaders.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-materials-shaders.qdoc index 73d4f66937e..abddc903a97 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-materials-shaders.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-materials-shaders.qdoc @@ -32,7 +32,7 @@ \title Materials and Shaders - \image studio-qtquick-3d-material.png "Material attached to model in Design mode" + \image studio-qtquick-3d-material.webp "Material attached to model in Design mode" Materials and shaders define how object surfaces are rendered in \QDS and live preview. As you change the properties of materials, new shaders are @@ -40,6 +40,10 @@ a shader depends on a combination of the properties that are set on it, and the context of the scene itself. + It is recommended that you use the \l {Material Editor and Browser} when + working with materials, but you can also add materials using the components + library. + The materials that you used in your imported scenes are imported to \QDS as \l{Qt Quick 3D} components. When you add a View3D component, it contains a DefaultMaterial component. You can use the following predefined Qt Quick @@ -62,171 +66,12 @@ defines an image and how the image is mapped to meshes in a 3D scene. For more information, see \l {Textures}. - You can modify material properties in the \uicontrol Properties view, as - instructed in the following sections. The availability of the properties - depends on the material type. + You can create and modify materials in + \uicontrol {Material Editor} and \uicontrol {Material Browser}. The availability + of the properties depends on the material type. - \image studio-qtquick-3d-default-material.png "DefaultMaterial properties" - - To enable the material to use vertex colors from the mesh, select the - \uicontrol {Enable vertex colors} check box. These are multiplied - by any other colors specified for the material. + \image studio-qtquick-3d-default-material.webp "DefaultMaterial properties" You can animate material properties in the \uicontrol Timeline view, as instructed in \l {Creating Timeline Animations}. - - \section1 Blending Colors - - To determine how the colors of a model blend with the colors of the models - behind it, set the \uicontrol {Blend mode} property. To make opaque objects - occlude the objects behind them, select \uicontrol {SourceOver}. - - For a lighter result, select \uicontrol Screen to blend colors using an - inverted multiply or \uicontrol ColorDodge to blend them by inverted - division. Color dodge procudes an even lighter result than screen. - - For a darker result, select \uicontrol Multiply to blend colors using a - multiply or \uicontrol ColorBurn to blend them by inverted division, where - the result also is inverted. Color burn produces an even darker result than - multiply. - - The screen and multiply modes are order-independent, so select them to - avoid \e popping, which can happen when using semi-opaque objects and - sorting the back and front faces or models. - - For a result with higher contrast, select \uicontrol Overlay, which is a mix - of the multiply and screen modes. - - \section1 Lighting Materials - - To set the lighting method for generating a material, use the - \uicontrol Lighting property. Select \uicontrol {Fragment lighting} to - calculate diffuse and specular lighting for each rendered pixel. Some - effects, such as Fresnel or a bump map, require fragment lighting. - - To skip lighting calculation, select \uicontrol {No lighting}. This is very - fast and quite effective when using image maps that do not need to be shaded - by lighting. - - To set the base color for the material, use the \uicontrol {Diffuse Color} - property. You can either use the color picker or specify a RBG value. Set - the diffuse color to black to create purely-specular materials, such as - metals or mirrors. To apply a texture to a material, set it as the value of - the \uicontrol {Diffuse map} property. Using a texture with transparency - also applies the alpha channel as an \uicontrol {Opacity map}. You can set - the opacity of the material independently of the model as the value of the - \uicontrol Opacity property. - - \section1 Self-Illuminating Materials - - To set the color and amount of self-illumination for a material, use the - \uicontrol {Emissive color} and \uicontrol {Emissive factor} properties. In - a scene with black ambient lighting, a material with an emissive factor of 0 - is black where the light does not shine on it. Setting the emissive factor - to 1 shows the material in its diffuse color instead. - - To use a Texture for specifying the emissive factor for different parts of - the material, set the \uicontrol {Emissive map} property. Using a grayscale - image does not affect the color of the result, while using a color image - produces glowing regions with the color affected by the emissive map. - - \section1 Using Highlights and Reflections - - You can control the highlights and reflections on a material by setting the - properties in the \uicontrol Specular group. You can use the color picker - or set a RGB value to specify the color used to adjust specular reflections. - Use white for no effect - - To use a color texture to modulate the amount and the color of specularity - across the surface of a material, set the \uicontrol {Specular map} - property. Set the \uicontrol {Specular amount} property to specify the - strength of specularity. This property does not affect the specular - reflection map, but it does affect the amount of reflections from a scene's - light probe. - - \note Unless your mesh is high-resolution, you may need to use fragment - lighting to get good specular highlights from scene lights. - - To determine how to calculate specular highlights for lights in the scene, - set the \uicontrol {Specular model}. In addition to the default mode, you - can use the GGX or Ward lighting model. - - To use a Texture for specular highlighting on a material, set the - \uicontrol {Reflection map} property. When the texture is applied using - environmental mapping (not UV mapping), the map appears to be reflecting - from the environment as you rotate the model. Specular reflection maps are - an easy way to add a high-quality look at a relatively low cost. - - To specify an image to use as the specular reflection map, set the - \uicontrol {Light probe} property. - - Crisp images cause your material to look very glossy. The more you - blur your image, the softer your material appears. - - To decrease head-on reflections (looking directly at the surface) - while maintaining reflections seen at grazing angles, set the - \uicontrol {Fresnel power} property. To select the angles to control, - set the \uicontrol {Index of refraction} property. - - To control the size of the specular highlights generated from lights and the - clarity of reflections in general, set the \uicontrol {Specular roughness} - property. Larger values increase the roughness, while softening specular - highlights and blurring reflections. To control the specular roughness of - the material using a Texture, set the \uicontrol {Roughness map property}. - - \section1 Simulating Geometry Displacement - - Specify the properties in the \uicontrol {Bump/Normal} group to simulate - fine geometry displacement across the surface of the material. Set the - \uicontrol {Bump map} property to use a grayscale texture for the - simulation. Brighter pixels indicate raised regions. - - To use a RGB image for simulation, set the \uicontrol {Normal map} property. - The RGB channels indicate XYZ normal deviations. - - The amount of displacement is controlled by the \uicontrol {Bump amount} - property. - - Bump and normal maps do not affect the silhouette of a model. To affect the - silhouette, set the \uicontrol {Displacement map} property. It specifies a - grayscale image used to offset the vertices of geometry across the surface - of the material. The \uicontrol {Displacement amount} property specifies the - offset amount. - - \section1 Specifying Material Translucency - - Set the properties in the \uicontrol Translucency group to control how much - light can pass through the material from behind. To use a grayscale texture, - specify it as the value of the \uicontrol {Translucency map} property. - - To specify the amount of light wrap for the translucency map, set the - \uicontrol {Diffuse light wrap} property. A value of 0 does not wrap the - light at all, while a value of 1 wraps the light all around the object. - - To specify the amount of falloff for the translucency based on - the angle of the normals of the object to the light source, set - the \uicontrol {Translucency falloff} property. - - \section1 Culling Faces - - Set the \uicontrol {Culling mode} property to determine whether the front - and back faces of a model are rendered. Culling modes check whether the - points in the polygon appear in clockwise or counter-clockwise order when - projected onto the screen. If front-facing polygons have a clockwise - winding, but the polygon projected on the screen has a counter-clockwise - winding, the projected polygon is rotated to face away from the camera and - is not rendered. Culling makes rendering objects quicker and more efficient - by reducing the number of polygons to draw. - - \section1 Applying Materials to Models - - To apply materials to models, you should first delete the default material, - and then drag-and-drop a new material from \l Assets to a model component - in \l Navigator. - - You can apply the same material to another component as well. Again, - delete the default material first. You should then select the component and - go to the \uicontrol Properties view. Find the \uicontrol Materials property, - select the \inlineimage icons/plus.png - icon, and choose the new material in the dropdown menu. */ diff --git a/doc/qtdesignstudio/src/views/qtquick-components-view.qdoc b/doc/qtdesignstudio/src/views/qtquick-components-view.qdoc index 464926ea351..2d800909d6d 100644 --- a/doc/qtdesignstudio/src/views/qtquick-components-view.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-components-view.qdoc @@ -25,7 +25,7 @@ /*! \page quick-components-view.html - \previouspage studio-3d-editor.html + \previouspage studio-material-editor.html \nextpage quick-assets.html \title Components diff --git a/doc/qtdesignstudio/src/views/studio-material-editor.qdoc b/doc/qtdesignstudio/src/views/studio-material-editor.qdoc new file mode 100644 index 00000000000..fcfa9823e9f --- /dev/null +++ b/doc/qtdesignstudio/src/views/studio-material-editor.qdoc @@ -0,0 +1,266 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Creator documentation. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** +****************************************************************************/ + +/*! + \page studio-material-editor.html + \previouspage studio-3d-editor.html + \nextpage quick-components-view.html + + + \title Material Editor and Browser + + In the \uicontrol {Material Editor} and \uicontrol {Material Browser} views, + you create and manage materials. + + \image material-editor-browser.webp "Material Editor and Browser" + + \section1 Creating a Material + + To create a new material, do one of the following: + + \list + \li In \uicontrol {Material Browser}, select \inlineimage icons/plus.png + . + \li In \uicontrol {Material Editor}, select \inlineimage icons/plus.png + . + \endlist + + \section1 Editing a Material + + To edit a material, select it in \uicontrol{Material Browser} and edit its + properties in \uicontrol{Material Editor}. If \uicontrol {Material Editor} + is closed, open it in one of the following ways: + + \list + \li In \uicontrol{Navigator}, right-click an object that has the material + assigned to it and select \uicontrol {Edit Material}. + \li In \uicontrol{Material Browser}, double-click a material. + \endlist + + \section1 Assigning a Material to an Object + + To assign a material to a 3D object in your project, first select the object + in \uicontrol Navigator or \uicontrol {3D Editor}. Then, do one of the + following: + + \list + \li In \uicontrol {Material Browser}, right-click the material and select + \uicontrol {Apply to Selected}. If there already is any material assigned + to the object, you can select whether to replace the material or to add + another material to the object. + \li In \uicontrol {Material Editor}, select + \inlineimage icons/apply-material.png + . This replaces any material already assigned to the object. + \endlist + + \section1 Removing a Material from an Object + + To remove an assigned material from an object: + \list 1 + \li In \uicontrol{Navigator}, select the object. + \li In \uicontrol{Properties}, select + \inlineimage icons/close.png + next to the material. + \image materials-remove-material.png + \endlist + + \section1 Using Texture Maps + + In \QDS you can add many different texture maps to your material. + + To add a texture map to a material: + \list 1 + \li Select the material in \uicontrol{Material Browser}. + \li From \uicontrol {Assets}, drag an image to the correct map field + in \uicontrol {Material Editor}. For example, to add a diffuse map, drag + the image to \uicontrol{Diffuse Map} in \uicontrol{Material Editor}. + \endlist + + \section2 Using a Reflection Map for Environmental Mapping + + To use a texture for environmental mapping, you need to set the mapping + mode to \e {environment}. + + To add a reflection map for environmental mapping to a material: + + \list 1 + \li Select the material in \uicontrol {Material Browser}. + \li From \uicontrol{Assets}, drag an image to + \uicontrol{Reflection Map}. + \li In \uicontrol {Navigator}, select + \inlineimage icons/filtericon.png + and then clear \uicontrol {Show Only Visible Components}. Now the + texture you just added to the material is visible in + \uicontrol {Navigator}. + \image navigator-material-texture.png + \li In \uicontrol {Navigator}, select the texture. + \li In \uicontrol {Properties}, set \uicontrol {Texture Mapping} to + \uicontrol {Environment}. + \endlist + + \section1 Blending Colors + + To determine how the colors of a model blend with the colors of the models + behind it, set the \uicontrol {Blend mode} property. To make opaque objects + occlude the objects behind them, select \uicontrol {SourceOver}. + + For a lighter result, select \uicontrol Screen to blend colors using an + inverted multiply or \uicontrol ColorDodge to blend them by inverted + division. Color dodge produces an even lighter result than screen. + + For a darker result, select \uicontrol Multiply to blend colors using a + multiply or \uicontrol ColorBurn to blend them by inverted division, where + the result also is inverted. Color burn produces an even darker result than + multiply. + + The screen and multiply modes are order-independent, so select them to + avoid \e popping, which can happen when using semi-opaque objects and + sorting the back and front faces or models. + + For a result with higher contrast, select \uicontrol Overlay, which is a mix + of the multiply and screen modes. + + \section1 Lighting Materials + + To set the lighting method for generating a material, use the + \uicontrol Lighting property. Select \uicontrol {Fragment lighting} to + calculate diffuse and specular lighting for each rendered pixel. Some + effects, such as Fresnel or a bump map, require fragment lighting. + + To skip lighting calculation, select \uicontrol {No lighting}. This is very + fast and quite effective when using image maps that do not need to be shaded + by lighting. + + To set the base color for the material, use the \uicontrol {Diffuse Color} + property. You can either use the color picker or specify a RBG value. Set + the diffuse color to black to create purely-specular materials, such as + metals or mirrors. To apply a texture to a material, set it as the value of + the \uicontrol {Diffuse map} property. Using a texture with transparency + also applies the alpha channel as an \uicontrol {Opacity map}. You can set + the opacity of the material independently of the model as the value of the + \uicontrol Opacity property. + + \section1 Self-Illuminating Materials + + To set the color and amount of self-illumination for a material, use the + \uicontrol {Emissive color} and \uicontrol {Emissive factor} properties. In + a scene with black ambient lighting, a material with an emissive factor of 0 + is black where the light does not shine on it. Setting the emissive factor + to 1 shows the material in its diffuse color instead. + + To use a Texture for specifying the emissive factor for different parts of + the material, set the \uicontrol {Emissive map} property. Using a grayscale + image does not affect the color of the result, while using a color image + produces glowing regions with the color affected by the emissive map. + + \section1 Using Highlights and Reflections + + You can control the highlights and reflections on a material by setting the + properties in the \uicontrol Specular group. You can use the color picker + or set a RGB value to specify the color used to adjust specular reflections. + Use white for no effect. + + To use a color texture to modulate the amount and the color of specularity + across the surface of a material, set the \uicontrol {Specular map} + property. Set the \uicontrol {Specular amount} property to specify the + strength of specularity. This property does not affect the specular + reflection map, but it does affect the amount of reflections from a scene's + light probe. + + \note Unless your mesh is high-resolution, you may need to use fragment + lighting to get good specular highlights from scene lights. + + To determine how to calculate specular highlights for lights in the scene, + set the \uicontrol {Specular model}. In addition to the default mode, you + can use the GGX or Ward lighting model. + + To use a Texture for specular highlighting on a material, set the + \uicontrol {Reflection map} property. When the texture is applied using + environmental mapping (not UV mapping), the map appears to be reflecting + from the environment as you rotate the model. Specular reflection maps are + an easy way to add a high-quality look at a relatively low cost. + + To specify an image to use as the specular reflection map, set the + \uicontrol {Light probe} property. + + Crisp images cause your material to look very glossy. The more you + blur your image, the softer your material appears. + + To decrease head-on reflections (looking directly at the surface) + while maintaining reflections seen at grazing angles, set the + \uicontrol {Fresnel power} property. To select the angles to control, + set the \uicontrol {Index of refraction} property. + + To control the size of the specular highlights generated from lights and the + clarity of reflections in general, set the \uicontrol {Specular roughness} + property. Larger values increase the roughness, while softening specular + highlights and blurring reflections. To control the specular roughness of + the material using a Texture, set the \uicontrol {Roughness map property}. + + \section1 Simulating Geometry Displacement + + Specify the properties in the \uicontrol {Bump/Normal} group to simulate + fine geometry displacement across the surface of the material. Set the + \uicontrol {Bump map} property to use a grayscale texture for the + simulation. Brighter pixels indicate raised regions. + + To use an image for simulation, set the \uicontrol {Normal map} property. + The RGB channels indicate XYZ normal deviations. + + The amount of displacement is controlled by the \uicontrol {Bump amount} + property. + + Bump and normal maps do not affect the silhouette of a model. To affect the + silhouette, set the \uicontrol {Displacement map} property. It specifies a + grayscale image used to offset the vertices of geometry across the surface + of the material. The \uicontrol {Displacement amount} property specifies the + offset amount. + + \section1 Specifying Material Translucency + + Set the properties in the \uicontrol Translucency group to control how much + light can pass through the material from behind. To use a grayscale texture, + specify it as the value of the \uicontrol {Translucency map} property. + + To specify the amount of light wrap for the translucency map, set the + \uicontrol {Diffuse light wrap} property. A value of 0 does not wrap the + light at all, while a value of 1 wraps the light all around the object. + + To specify the amount of falloff for the translucency based on + the angle of the normals of the object to the light source, set + the \uicontrol {Translucency falloff} property. + + \section1 Culling Faces + + Set the \uicontrol {Culling mode} property to determine whether the front + and back faces of a model are rendered. Culling modes check whether the + points in the polygon appear in clockwise or counter-clockwise order when + projected onto the screen. If front-facing polygons have a clockwise + winding, but the polygon projected on the screen has a counter-clockwise + winding, the projected polygon is rotated to face away from the camera and + is not rendered. Culling makes rendering objects quicker and more efficient + by reducing the number of polygons to draw. + +*/ From d8c605179ad4934c5f9a2060dd398a13821336c4 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Thu, 9 Jun 2022 19:40:23 +0200 Subject: [PATCH 14/53] QmlDesigner: Add option for smooth rendering in form editor Smooth rendering turns on MSAA and doubles the resolution for rendered items. With this option enabled everything stays smooth when zooming in. Around factor 8-10 pixels become clearly visible again, but it still looks relatively smooth. I added both MSAA and increased the resolution to one option, for simplicity. The smooth mode takes 4 times the shared memory, which should not be an issue in most cases. For now, the option is not the default. Task-number: QDS-7129 Task-number: QDS-7128 Change-Id: I8a778650bb40f8ba796960db9bc966e8a1efff4e Reviewed-by: Miikka Heikkinen --- .../instances/qt5nodeinstanceserver.cpp | 11 +++++++++-- .../instances/quickitemnodeinstance.cpp | 4 ++-- .../designercore/instances/puppetcreator.cpp | 6 ++++++ src/plugins/qmldesigner/designersettings.cpp | 1 + src/plugins/qmldesigner/designersettings.h | 1 + src/plugins/qmldesigner/settingspage.cpp | 2 ++ src/plugins/qmldesigner/settingspage.ui | 17 +++++++++++++++++ 7 files changed, 38 insertions(+), 4 deletions(-) diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.cpp index 32f63a8cd66..e7a98b74275 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.cpp @@ -405,6 +405,9 @@ QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item) QQuickItemPrivate *pItem = QQuickItemPrivate::get(item); const bool renderEffects = qEnvironmentVariableIsSet("QMLPUPPET_RENDER_EFFECTS"); + const bool smoothRendering = qEnvironmentVariableIsSet("QMLPUPPET_SMOOTH_RENDERING"); + + int scaleFactor = smoothRendering ? 2 : 1; if (renderEffects) { if (parentEffectItem(item)) @@ -470,6 +473,8 @@ QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item) // us to render it to a texture that we can grab to an image. QSGRenderContext *rc = QQuickWindowPrivate::get(m_viewData.window.data())->context; QSGLayer *layer = rc->sceneGraphContext()->createLayer(rc); + if (smoothRendering) + layer->setSamples(4); layer->setItem(pItem->itemNode()); layer->setRect(QRectF(renderBoundingRect.x(), @@ -478,8 +483,8 @@ QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item) -renderBoundingRect.height())); const QSize minSize = rc->sceneGraphContext()->minimumFBOSize(); - layer->setSize(QSize(qMax(minSize.width(), int(renderBoundingRect.width())), - qMax(minSize.height(), int(renderBoundingRect.height())))); + layer->setSize(QSize(qMax(minSize.width(), int(renderBoundingRect.width() * scaleFactor)), + qMax(minSize.height(), int(renderBoundingRect.height() * scaleFactor)))); layer->scheduleUpdate(); if (layer->updateTexture()) @@ -489,6 +494,8 @@ QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item) delete layer; layer = nullptr; + + renderImage.setDevicePixelRatio(scaleFactor); }); m_viewData.renderControl->render(); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.cpp index 41d0015ce56..5b1f3171adb 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.cpp @@ -504,12 +504,12 @@ QImage QuickItemNodeInstance::renderImage() const if (s_unifiedRenderPath) { renderImage = nodeInstanceServer()->grabWindow(); renderImage = renderImage.copy(renderBoundingRect.toRect()); + /* When grabbing an offscren window the device pixel ratio is 1 */ + renderImage.setDevicePixelRatio(1); } else { renderImage = nodeInstanceServer()->grabItem(quickItem()); } - /* When grabbing an offscren window the device pixel ratio is 1 */ - renderImage.setDevicePixelRatio(1); #endif return renderImage; diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp index a437e5c0be0..7d4beeacd8d 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp +++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp @@ -504,6 +504,12 @@ QProcessEnvironment PuppetCreator::processEnvironment() const #ifndef QMLDESIGNER_TEST const QString controlsStyle = m_designerSettings.value(DesignerSettingsKey:: CONTROLS_STYLE).toString(); + + const bool smoothRendering = m_designerSettings.value(DesignerSettingsKey::SMOOTH_RENDERING) + .toBool(); + + if (smoothRendering) + environment.set("QMLPUPPET_SMOOTH_RENDERING", "true"); #else const QString controlsStyle; #endif diff --git a/src/plugins/qmldesigner/designersettings.cpp b/src/plugins/qmldesigner/designersettings.cpp index 20083068c16..5474fd50d34 100644 --- a/src/plugins/qmldesigner/designersettings.cpp +++ b/src/plugins/qmldesigner/designersettings.cpp @@ -82,6 +82,7 @@ void DesignerSettings::fromSettings(QSettings *settings) restoreValue(settings, DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET, true); const QStringList defaultValue = QStringList() << "#222222" << "#999999"; restoreValue(settings, DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR, defaultValue); + restoreValue(settings, DesignerSettingsKey::SMOOTH_RENDERING, false); settings->endGroup(); settings->endGroup(); diff --git a/src/plugins/qmldesigner/designersettings.h b/src/plugins/qmldesigner/designersettings.h index 8c249fc65e0..4dbee7752ba 100644 --- a/src/plugins/qmldesigner/designersettings.h +++ b/src/plugins/qmldesigner/designersettings.h @@ -72,6 +72,7 @@ const char COLOR_PALETTE_FAVORITE[] = "ColorPaletteFavorite"; const char ALWAYS_DESIGN_MODE[] = "AlwaysDesignMode"; const char DISABLE_ITEM_LIBRARY_UPDATE_TIMER[] = "DisableItemLibraryUpdateTimer"; const char ASK_BEFORE_DELETING_ASSET[] = "AskBeforeDeletingAsset"; +const char SMOOTH_RENDERING[] = "SmoothRendering"; } class QMLDESIGNERCORE_EXPORT DesignerSettings : public QHash diff --git a/src/plugins/qmldesigner/settingspage.cpp b/src/plugins/qmldesigner/settingspage.cpp index 1af226ae49f..700402680a9 100644 --- a/src/plugins/qmldesigner/settingspage.cpp +++ b/src/plugins/qmldesigner/settingspage.cpp @@ -182,6 +182,7 @@ DesignerSettings SettingsPageWidget::settings() const m_ui.designerAlwaysDesignModeCheckBox->isChecked()); settings.insert(DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET, m_ui.askBeforeDeletingAssetCheckBox->isChecked()); + settings.insert(DesignerSettingsKey::SMOOTH_RENDERING, m_ui.smoothRendering->isChecked()); return settings; } @@ -264,6 +265,7 @@ void SettingsPageWidget::setSettings(const DesignerSettings &settings) m_ui.emulationGroupBox->setVisible(showAdvancedFeatures); m_ui.debugGroupBox->setVisible(showAdvancedFeatures); m_ui.featureTimelineEditorCheckBox->setVisible(standaloneMode); + m_ui.smoothRendering->setChecked(settings.value(DesignerSettingsKey::SMOOTH_RENDERING).toBool()); } void SettingsPageWidget::apply() diff --git a/src/plugins/qmldesigner/settingspage.ui b/src/plugins/qmldesigner/settingspage.ui index eace952540a..004f8a68eb6 100644 --- a/src/plugins/qmldesigner/settingspage.ui +++ b/src/plugins/qmldesigner/settingspage.ui @@ -99,6 +99,23 @@ + + + + Enable Smooth Rendering in Form Editor + + + + + + + + + + Smooth Rendering: + + + From 36dbc62a1d67f02241e5c5860af083d7f4a0d7b5 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 7 Jun 2022 16:27:28 +0300 Subject: [PATCH 15/53] QmlDesigner: Queue puppet processes during import On some systems launcing multiple simultaneous import processes causes imports to fail. Fixed by only launching single process at a time and queuing the rest. Fixes: QDS-7107 Change-Id: I330c5920dcbd74d3b4f2e7f40899795a4fbaf3ac Reviewed-by: Thomas Hartmann --- .../qml2puppet/iconrenderer/iconrenderer.cpp | 3 +- .../itemlibrary/itemlibraryassetimporter.cpp | 166 ++++++++++-------- .../itemlibrary/itemlibraryassetimporter.h | 16 +- 3 files changed, 99 insertions(+), 86 deletions(-) diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.cpp index 2dd653273cd..76deb3a3b39 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.cpp @@ -198,8 +198,7 @@ void IconRenderer::finishCreateIcon() render(saveFile); - // Allow little time for file operations to finish - QTimer::singleShot(1000, qGuiApp, &QGuiApplication::quit); + QTimer::singleShot(0, qGuiApp, &QGuiApplication::quit); } void IconRenderer::render(const QString &fileName) diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp index e3aae0cb7d5..8e8087dd214 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include @@ -90,21 +91,16 @@ void ItemLibraryAssetImporter::importQuick3D(const QStringList &inputFiles, if (!isCancelled()) { const auto parseData = m_parseData; - for (const auto &pd : parseData) { - if (!startImportProcess(pd)) { - addError(tr("Failed to start import 3D asset process."), - pd.sourceInfo.absoluteFilePath()); - m_parseData.remove(pd.importId); - } - } + for (const auto &pd : parseData) + m_puppetQueue.append(pd.importId); + startNextImportProcess(); } if (!isCancelled()) { // Wait for puppet processes to finish - if (m_qmlPuppetProcesses.empty()) { + if (m_puppetQueue.isEmpty() && !m_puppetProcess) { postImport(); } else { - m_qmlPuppetCount = static_cast(m_qmlPuppetProcesses.size()); const QString progressTitle = tr("Importing 3D assets."); addInfo(progressTitle); notifyProgress(0, progressTitle); @@ -142,21 +138,14 @@ void ItemLibraryAssetImporter::addInfo(const QString &infoMsg, const QString &sr emit infoReported(infoMsg, srcPath); } -void ItemLibraryAssetImporter::importProcessFinished(int exitCode, QProcess::ExitStatus exitStatus, - int importId) +void ItemLibraryAssetImporter::importProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitCode) - ++m_qmlImportFinishedCount; + m_puppetProcess.reset(); - m_qmlPuppetProcesses.erase( - std::remove_if(m_qmlPuppetProcesses.begin(), m_qmlPuppetProcesses.end(), - [&](const auto &entry) { - return !entry || entry->state() == QProcess::NotRunning; - })); - - if (m_parseData.contains(importId)) { - const ParseData &pd = m_parseData[importId]; + if (m_parseData.contains(m_currentImportId)) { + const ParseData &pd = m_parseData[m_currentImportId]; QString errStr; if (exitStatus == QProcess::ExitStatus::CrashExit) { errStr = tr("Import process crashed."); @@ -179,15 +168,19 @@ void ItemLibraryAssetImporter::importProcessFinished(int exitCode, QProcess::Exi addError(tr("Asset import process failed: \"%1\".") .arg(pd.sourceInfo.absoluteFilePath())); addError(errStr); - m_parseData.remove(importId); + m_parseData.remove(m_currentImportId); } } - if (m_qmlImportFinishedCount == m_qmlPuppetCount) { + int finishedCount = m_parseData.size() - m_puppetQueue.size(); + if (!m_puppetQueue.isEmpty()) + startNextImportProcess(); + + if (m_puppetQueue.isEmpty() && !m_puppetProcess) { notifyProgress(100); QTimer::singleShot(0, this, &ItemLibraryAssetImporter::postImport); } else { - notifyProgress(int(100. * (double(m_qmlImportFinishedCount) / double(m_qmlPuppetCount)))); + notifyProgress(int(100. * (double(finishedCount) / double(m_parseData.size())))); } } @@ -196,17 +189,17 @@ void ItemLibraryAssetImporter::iconProcessFinished(int exitCode, QProcess::ExitS Q_UNUSED(exitCode) Q_UNUSED(exitStatus) - m_qmlPuppetProcesses.erase( - std::remove_if(m_qmlPuppetProcesses.begin(), m_qmlPuppetProcesses.end(), - [&](const auto &entry) { - return !entry || entry->state() == QProcess::NotRunning; - })); + m_puppetProcess.reset(); - if (m_qmlPuppetProcesses.empty()) { + int finishedCount = m_parseData.size() - m_puppetQueue.size(); + if (!m_puppetQueue.isEmpty()) + startNextIconProcess(); + + if (m_puppetQueue.isEmpty() && !m_puppetProcess) { notifyProgress(100); QTimer::singleShot(0, this, &ItemLibraryAssetImporter::finalizeQuick3DImport); } else { - notifyProgress(int(100. * (1. - (double(m_qmlPuppetProcesses.size()) / double(m_qmlPuppetCount))))); + notifyProgress(int(100. * (double(finishedCount) / double(m_parseData.size())))); } } @@ -225,11 +218,11 @@ void ItemLibraryAssetImporter::reset() m_tempDir = new QTemporaryDir; m_importFiles.clear(); m_overwrittenImports.clear(); - m_qmlPuppetProcesses.clear(); - m_qmlPuppetCount = 0; - m_qmlImportFinishedCount = 0; + m_puppetProcess.reset(); m_parseData.clear(); m_requiredImports.clear(); + m_currentImportId = 0; + m_puppetQueue.clear(); } void ItemLibraryAssetImporter::parseFiles(const QStringList &filePaths, @@ -351,7 +344,7 @@ bool ItemLibraryAssetImporter::preParseQuick3DAsset(const QString &file, ParseDa return true; } -void ItemLibraryAssetImporter::postParseQuick3DAsset(const ParseData &pd) +void ItemLibraryAssetImporter::postParseQuick3DAsset(ParseData &pd) { QDir outDir = pd.outDir; if (pd.originalAssetName != pd.assetName) { @@ -452,8 +445,10 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(const ParseData &pd) "QtQuick3D", impVersionStr)); } } - if (impVersionMajor > 0 && impVersionMajor < 6 - && startIconProcess(24, iconFileName, qmlIt.filePath())) { + if (impVersionMajor > 0 && impVersionMajor < 6) { + pd.iconFile = iconFileName; + pd.iconSource = qmlIt.filePath(); + m_puppetQueue.append(pd.importId); // Since icon is generated by external process, the file won't be // ready for asset gathering below, so assume its generation succeeds // and add it now. @@ -589,84 +584,101 @@ ItemLibraryAssetImporter::OverwriteResult ItemLibraryAssetImporter::confirmAsset return OverwriteResult::Skip; } -bool ItemLibraryAssetImporter::startImportProcess(const ParseData &pd) +void ItemLibraryAssetImporter::startNextImportProcess() { + if (m_puppetQueue.isEmpty()) + return; + auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); Model *model = doc ? doc->currentModel() : nullptr; if (model) { PuppetCreator puppetCreator(doc->currentTarget(), model); puppetCreator.createQml2PuppetExecutableIfMissing(); - QStringList puppetArgs; - QJsonDocument optDoc(pd.options); - puppetArgs << "--import3dAsset" << pd.sourceInfo.absoluteFilePath() - << pd.outDir.absolutePath() << QString::fromUtf8(optDoc.toJson()); + bool done = false; + while (!m_puppetQueue.isEmpty() && !done) { + const ParseData pd = m_parseData.value(m_puppetQueue.takeLast()); + QStringList puppetArgs; + QJsonDocument optDoc(pd.options); - QProcessUniquePointer process = puppetCreator.createPuppetProcess( - "custom", - {}, - [&] {}, - [&](int exitCode, QProcess::ExitStatus exitStatus) { - importProcessFinished(exitCode, exitStatus, pd.importId); - }, - puppetArgs); + puppetArgs << "--import3dAsset" << pd.sourceInfo.absoluteFilePath() + << pd.outDir.absolutePath() << QString::fromUtf8(optDoc.toJson()); - if (process->waitForStarted(5000)) { - m_qmlPuppetProcesses.push_back(std::move(process)); - return true; - } else { - process.reset(); + m_currentImportId = pd.importId; + m_puppetProcess = puppetCreator.createPuppetProcess( + "custom", + {}, + [&] {}, + [&](int exitCode, QProcess::ExitStatus exitStatus) { + importProcessFinished(exitCode, exitStatus); + }, + puppetArgs); + + if (m_puppetProcess->waitForStarted(10000)) { + done = true; + } else { + addError(tr("Failed to start import 3D asset process."), + pd.sourceInfo.absoluteFilePath()); + m_parseData.remove(pd.importId); + m_puppetProcess.reset(); + } } } - return false; } -bool ItemLibraryAssetImporter::startIconProcess(int size, const QString &iconFile, - const QString &iconSource) +void ItemLibraryAssetImporter::startNextIconProcess() { + if (m_puppetQueue.isEmpty()) + return; + auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); Model *model = doc ? doc->currentModel() : nullptr; if (model) { PuppetCreator puppetCreator(doc->currentTarget(), model); puppetCreator.createQml2PuppetExecutableIfMissing(); - QStringList puppetArgs; - puppetArgs << "--rendericon" << QString::number(size) << iconFile << iconSource; - QProcessUniquePointer process = puppetCreator.createPuppetProcess( - "custom", - {}, - [&] {}, - [&](int exitCode, QProcess::ExitStatus exitStatus) { - iconProcessFinished(exitCode, exitStatus); - }, - puppetArgs); - if (process->waitForStarted(5000)) { - m_qmlPuppetProcesses.push_back(std::move(process)); - return true; - } else { - process.reset(); + bool done = false; + while (!m_puppetQueue.isEmpty() && !done) { + const ParseData pd = m_parseData.value(m_puppetQueue.takeLast()); + QStringList puppetArgs; + puppetArgs << "--rendericon" << QString::number(24) << pd.iconFile << pd.iconSource; + m_puppetProcess = puppetCreator.createPuppetProcess( + "custom", + {}, + [&] {}, + [&](int exitCode, QProcess::ExitStatus exitStatus) { + iconProcessFinished(exitCode, exitStatus); + }, + puppetArgs); + + if (m_puppetProcess->waitForStarted(10000)) { + done = true; + } else { + addError(tr("Failed to start icon generation process."), + pd.sourceInfo.absoluteFilePath()); + m_puppetProcess.reset(); + } } } - return false; } void ItemLibraryAssetImporter::postImport() { - Q_ASSERT(m_qmlPuppetProcesses.empty()); + QTC_ASSERT(m_puppetQueue.isEmpty() && !m_puppetProcess, return); if (!isCancelled()) { - for (const auto &pd : qAsConst(m_parseData)) + for (auto &pd : m_parseData) postParseQuick3DAsset(pd); + startNextIconProcess(); } if (!isCancelled()) { // Wait for icon generation processes to finish - if (m_qmlPuppetProcesses.empty()) { + if (m_puppetQueue.isEmpty() && !m_puppetProcess) { finalizeQuick3DImport(); } else { - m_qmlPuppetCount = static_cast(m_qmlPuppetProcesses.size()); const QString progressTitle = tr("Generating icons."); addInfo(progressTitle); notifyProgress(0, progressTitle); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h index 83be9af5f4e..1f0009eaab5 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h @@ -74,7 +74,7 @@ signals: void importFinished(); private slots: - void importProcessFinished(int exitCode, QProcess::ExitStatus exitStatus, int importId); + void importProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); void iconProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); private: @@ -87,6 +87,8 @@ private: QString assetName; QString originalAssetName; int importId; + QString iconFile; + QString iconSource; }; void notifyFinished(); @@ -96,7 +98,7 @@ private: const QSet &preselectedFilesForOverwrite); bool preParseQuick3DAsset(const QString &file, ParseData &pd, const QSet &preselectedFilesForOverwrite); - void postParseQuick3DAsset(const ParseData &pd); + void postParseQuick3DAsset(ParseData &pd); void copyImportedFiles(); void notifyProgress(int value, const QString &text); @@ -110,8 +112,8 @@ private: }; OverwriteResult confirmAssetOverwrite(const QString &assetName); - bool startImportProcess(const ParseData &pd); - bool startIconProcess(int size, const QString &iconFile, const QString &iconSource); + void startNextImportProcess(); + void startNextIconProcess(); void postImport(); void finalizeQuick3DImport(); QString sourceSceneTargetFilePath(const ParseData &pd); @@ -122,12 +124,12 @@ private: bool m_cancelled = false; QString m_importPath; QTemporaryDir *m_tempDir = nullptr; - std::vector m_qmlPuppetProcesses; - int m_qmlPuppetCount = 0; - int m_qmlImportFinishedCount = 0; + QProcessUniquePointer m_puppetProcess; int m_importIdCounter = 0; + int m_currentImportId = 0; QHash m_parseData; QString m_progressTitle; QList m_requiredImports; + QList m_puppetQueue; }; } // QmlDesigner From dedbbc75b564b0a93c89688d76a7cab6dbc06046 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Fri, 10 Jun 2022 15:23:16 +0300 Subject: [PATCH 16/53] QmlDesigner: Apply root item transform in Node component previews When generating a preview from a component with a 3D node for a root, the root Node transform is now applied when calculating camera zoom. Fixes: QDS-7131 Change-Id: I73054a09b3e82868c999ef6f9797dc941e625b33 Reviewed-by: Mahmoud Badri --- .../qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNodeView.qml | 2 +- .../qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNodeView.qml | 2 +- .../qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp | 6 +++--- .../qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNodeView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNodeView.qml index 5c8b9e1cd72..b497b0419a1 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNodeView.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNodeView.qml @@ -37,7 +37,7 @@ View3D { function fitToViewPort(closeUp) { // The magic number is the distance from camera default pos to origin - _generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, sourceModel, root, + _generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, model, root, 1040, closeUp); } diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNodeView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNodeView.qml index 1762e3c9a4d..4da76ea097f 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNodeView.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNodeView.qml @@ -37,7 +37,7 @@ View3D { function fitToViewPort(closeUp) { // The magic number is the distance from camera default pos to origin - _generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, sourceModel, root, + _generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, model, root, 1040, closeUp); } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp index 74c2eb270ef..590785a30e3 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp @@ -812,7 +812,7 @@ QVector3D GeneralHelper::pivotScenePosition(QQuick3DNode *node) const // Calculate bounds for given node, including all child nodes. // Returns true if the tree contains at least one Model node. bool GeneralHelper::getBounds(QQuick3DViewport *view3D, QQuick3DNode *node, QVector3D &minBounds, - QVector3D &maxBounds, bool recursive) + QVector3D &maxBounds) { if (!node) { const float halfExtent = 100.f; @@ -825,7 +825,7 @@ bool GeneralHelper::getBounds(QQuick3DViewport *view3D, QQuick3DNode *node, QVec auto nodePriv = QQuick3DObjectPrivate::get(node); auto renderNode = static_cast(nodePriv->spatialNode); - if (recursive && renderNode) { + if (renderNode) { #if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) if (renderNode->flags.testFlag(QSSGRenderNode::Flag::TransformDirty)) renderNode->calculateLocalTransform(); @@ -850,7 +850,7 @@ bool GeneralHelper::getBounds(QQuick3DViewport *view3D, QQuick3DNode *node, QVec if (auto childNode = qobject_cast(child)) { QVector3D newMinBounds = minBounds; QVector3D newMaxBounds = maxBounds; - bool childHasModel = getBounds(view3D, childNode, newMinBounds, newMaxBounds, true); + bool childHasModel = getBounds(view3D, childNode, newMinBounds, newMaxBounds); // Ignore any subtrees that do not have Model in them as we don't need those // for visual bounds calculations if (childHasModel) { diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h index 5bb1fa1662f..8014a711875 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h @@ -130,7 +130,7 @@ private: void handlePendingToolStateUpdate(); QVector3D pivotScenePosition(QQuick3DNode *node) const; bool getBounds(QQuick3DViewport *view3D, QQuick3DNode *node, QVector3D &minBounds, - QVector3D &maxBounds, bool recursive = false); + QVector3D &maxBounds); QTimer m_overlayUpdateTimer; QTimer m_toolStateUpdateTimer; From 13c1b6f2373f3da6198e1111a96f5aee1036023a Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Fri, 10 Jun 2022 21:08:56 +0200 Subject: [PATCH 17/53] QmlDesigner: Do not apply smooth rendering if root is a 3D node Task-number: QDS-7136 Change-Id: I5260153b8d6131160a8d4b67418e81c6f6e4d862 Reviewed-by: Miikka Heikkinen --- .../qml2puppet/instances/qt5nodeinstanceserver.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.cpp index e7a98b74275..3ccd6c60dce 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.cpp @@ -407,8 +407,6 @@ QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item) const bool renderEffects = qEnvironmentVariableIsSet("QMLPUPPET_RENDER_EFFECTS"); const bool smoothRendering = qEnvironmentVariableIsSet("QMLPUPPET_SMOOTH_RENDERING"); - int scaleFactor = smoothRendering ? 2 : 1; - if (renderEffects) { if (parentEffectItem(item)) return renderImage; @@ -430,17 +428,20 @@ QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item) ServerNodeInstance instance = instanceForObject(item); + const bool rootIs3DObject = rootIsRenderable3DObject(); + // Setting layer enabled to false messes up the bounding rect. // Therefore we calculate it upfront. QRectF renderBoundingRect; if (instance.isValid()) renderBoundingRect = instance.boundingRect(); - - else if (rootIsRenderable3DObject()) + else if (rootIs3DObject) renderBoundingRect = item->boundingRect(); else renderBoundingRect = ServerNodeInstance::effectAdjustedBoundingRect(item); + const int scaleFactor = (smoothRendering && !rootIs3DObject) ? 2 : 1; + // Hide immediate children that have instances and are QQuickItems so we get only // the parent item's content, as compositing is handled on creator side. QSet layerChildren; @@ -521,7 +522,6 @@ QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item) if (!isLayerEnabled(pItem)) pItem->derefFromEffectItem(false); - #else Q_UNUSED(item) #endif From 7ae288d355b9763ed495c1ebbf198809ed7e50f0 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Fri, 10 Jun 2022 21:10:52 +0200 Subject: [PATCH 18/53] QmlDesigner: Use changed properties to update 3D preview EffectReference is always dirty and it is the only dirty flag. Instead, we update now if any property changed. Task-number: QDS-7079 Change-Id: I157cf2d3b15120c33f4a6b3f53e526d555c7f80c Reviewed-by: Miikka Heikkinen --- .../qml2puppet/instances/qt5rendernodeinstanceserver.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5rendernodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5rendernodeinstanceserver.cpp index 2ff23395761..59e7a4aa821 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5rendernodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5rendernodeinstanceserver.cpp @@ -111,8 +111,6 @@ void Qt5RenderNodeInstanceServer::collectItemChangesAndSendChangeCommands() } } - clearChangedPropertyList(); - if (Internal::QuickItemNodeInstance::unifiedRenderPath()) { if (windowDirty) nodeInstanceClient()->pixmapChanged(createPixmapChangedCommand({rootNodeInstance()})); @@ -134,13 +132,15 @@ void Qt5RenderNodeInstanceServer::collectItemChangesAndSendChangeCommands() } if (rootIsRenderable3DObject() && rootNodeInstance().contentItem() - && DesignerSupport::isDirty(rootNodeInstance().contentItem(), - DesignerSupport::AllMask) + && !changedPropertyList().isEmpty() && nodeInstanceClient()->bytesToWrite() < 10000) { + Internal::QuickItemNodeInstance::updateDirtyNode(rootNodeInstance().contentItem()); nodeInstanceClient()->pixmapChanged(createPixmapChangedCommand({rootNodeInstance()})); } + clearChangedPropertyList(); + inFunction = false; } } From b307182e7694c433dbd31caa491696c1321c1361 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Mon, 13 Jun 2022 14:47:15 +0300 Subject: [PATCH 19/53] QmlDesigner: Fix occasional creator side crash related to puppet reset Change-Id: Iee032937b89b8ffcaa012c21b7cac029e1b286cf Reviewed-by: Mahmoud Badri --- .../qmldesigner/designercore/instances/nodeinstanceview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index db22d27fc3d..278d65fcd9c 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -1717,7 +1717,7 @@ void NodeInstanceView::view3DAction(const View3DActionCommand &command) void NodeInstanceView::requestModelNodePreviewImage(const ModelNode &node, const ModelNode &renderNode) { - if (node.isValid()) { + if (m_nodeInstanceServer && node.isValid()) { auto instance = instanceForModelNode(node); if (instance.isValid()) { qint32 renderItemId = -1; From 50aadacb6e670597fc308d409bcdae96e7efa9c0 Mon Sep 17 00:00:00 2001 From: Samuel Ghinet Date: Wed, 8 Jun 2022 18:00:09 +0300 Subject: [PATCH 20/53] QDS Editor 3D: Allow the user to select (and reset) the color of grid lines Task-number: QDS-7122 Change-Id: I686269e61b53bd5e3d5d1225376930612d869072 Reviewed-by: Miikka Heikkinen Reviewed-by: Mahmoud Badri --- .../qmlpuppet/commands/createscenecommand.h | 7 +- .../qmlpuppet/commands/view3dactioncommand.h | 1 + .../qmlpuppet/mockfiles/qt5/EditView3D.qml | 18 ++- .../qmlpuppet/mockfiles/qt5/HelperGrid.qml | 1 + .../qmlpuppet/mockfiles/qt5/SceneView3D.qml | 1 + .../qmlpuppet/mockfiles/qt6/EditView3D.qml | 18 ++- .../qmlpuppet/mockfiles/qt6/HelperGrid.qml | 1 + .../qmlpuppet/mockfiles/qt6/SceneView3D.qml | 1 + .../qt5informationnodeinstanceserver.cpp | 30 +++-- .../qt5informationnodeinstanceserver.h | 2 +- src/plugins/qmldesigner/CMakeLists.txt | 1 + .../edit3d/backgroundcolorselection.cpp | 105 +++++++----------- .../edit3d/backgroundcolorselection.h | 13 ++- .../components/edit3d/edit3dactions.cpp | 3 +- .../components/edit3d/edit3dview.cpp | 99 ++++++++++++----- .../components/edit3d/edit3dview.h | 6 +- .../components/edit3d/edit3dviewconfig.h | 98 ++++++++++++++++ .../instances/nodeinstanceview.cpp | 31 ++---- src/plugins/qmldesigner/designersettings.cpp | 1 + src/plugins/qmldesigner/designersettings.h | 1 + .../qmldesigner/qmldesignerconstants.h | 1 + src/plugins/qmldesigner/qmldesignerplugin.qbs | 1 + 22 files changed, 299 insertions(+), 141 deletions(-) create mode 100644 src/plugins/qmldesigner/components/edit3d/edit3dviewconfig.h diff --git a/share/qtcreator/qml/qmlpuppet/commands/createscenecommand.h b/share/qtcreator/qml/qmlpuppet/commands/createscenecommand.h index c69d478875d..47196df8d24 100644 --- a/share/qtcreator/qml/qmlpuppet/commands/createscenecommand.h +++ b/share/qtcreator/qml/qmlpuppet/commands/createscenecommand.h @@ -61,7 +61,8 @@ public: QSize captureImageMinimumSize, QSize captureImageMaximumSize, qint32 stateInstanceId, - const QList &edit3dBackgroundColor) + const QList &edit3dBackgroundColor, + const QColor &edit3dGridColor) : instances(instanceContainer) , reparentInstances(reparentContainer) , ids(idVector) @@ -78,6 +79,7 @@ public: , captureImageMaximumSize(captureImageMaximumSize) , stateInstanceId{stateInstanceId} , edit3dBackgroundColor{edit3dBackgroundColor} + , edit3dGridColor{edit3dGridColor} {} friend QDataStream &operator<<(QDataStream &out, const CreateSceneCommand &command) @@ -98,6 +100,7 @@ public: out << command.captureImageMinimumSize; out << command.captureImageMaximumSize; out << command.edit3dBackgroundColor; + out << command.edit3dGridColor; return out; } @@ -120,6 +123,7 @@ public: in >> command.captureImageMinimumSize; in >> command.captureImageMaximumSize; in >> command.edit3dBackgroundColor; + in >> command.edit3dGridColor; return in; } @@ -141,6 +145,7 @@ public: QSize captureImageMaximumSize; qint32 stateInstanceId = 0; QList edit3dBackgroundColor; + QColor edit3dGridColor; }; QDebug operator<<(QDebug debug, const CreateSceneCommand &command); diff --git a/share/qtcreator/qml/qmlpuppet/commands/view3dactioncommand.h b/share/qtcreator/qml/qmlpuppet/commands/view3dactioncommand.h index cc3611df764..a1dc133032e 100644 --- a/share/qtcreator/qml/qmlpuppet/commands/view3dactioncommand.h +++ b/share/qtcreator/qml/qmlpuppet/commands/view3dactioncommand.h @@ -57,6 +57,7 @@ public: ParticlesRestart, ParticlesSeek, SelectBackgroundColor, + SelectGridColor, ResetBackgroundColor, }; diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml index 9a95ca34b98..09a4ebdc3f3 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml @@ -47,6 +47,7 @@ Item { property alias contentItem: contentItem property color backgroundGradientColorStart: "#222222" property color backgroundGradientColorEnd: "#999999" + property color gridColor: "#aaaaaa" enum SelectionMode { Item, Group } enum TransformMode { Move, Rotate, Scale } @@ -96,12 +97,14 @@ Item { {"usePerspective": usePerspective, "showSceneLight": showEditLight, "showGrid": showGrid, + "gridColor": gridColor, "importScene": activeScene, "cameraZoomFactor": cameraControl._zoomFactor, "z": 1}); editView.usePerspective = Qt.binding(function() {return usePerspective;}); editView.showSceneLight = Qt.binding(function() {return showEditLight;}); editView.showGrid = Qt.binding(function() {return showGrid;}); + editView.gridColor = Qt.binding(function() {return gridColor;}); editView.cameraZoomFactor = Qt.binding(function() {return cameraControl._zoomFactor;}); selectionBoxes.length = 0; @@ -217,10 +220,19 @@ Item { function updateViewStates(viewStates) { if ("selectBackgroundColor" in viewStates) { - var color = viewStates.selectBackgroundColor - backgroundGradientColorStart = color[0]; - backgroundGradientColorEnd = color[1]; + if (Array.isArray(viewStates.selectBackgroundColor)) { + var colors = viewStates.selectBackgroundColor + backgroundGradientColorStart = colors[0]; + backgroundGradientColorEnd = colors[1]; + } else { + var color = viewStates.selectBackgroundColor + backgroundGradientColorStart = color; + backgroundGradientColorEnd = color; + } } + + if ("selectGridColor" in viewStates) + viewRoot.gridColor = viewStates.selectGridColor } // If resetToDefault is true, tool states not specifically set to anything will be reset to diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/HelperGrid.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/HelperGrid.qml index e450a5a796c..d9df67fe7c4 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/HelperGrid.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/HelperGrid.qml @@ -33,6 +33,7 @@ Node { property alias lines: gridGeometry.lines property alias step: gridGeometry.step property alias subdivAlpha: subGridMaterial.opacity + property alias gridColor: mainGridMaterial.diffuseColor eulerRotation.x: 90 diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/SceneView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/SceneView3D.qml index 7a376179d82..4032e245ed0 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/SceneView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/SceneView3D.qml @@ -32,6 +32,7 @@ View3D { property bool usePerspective: false property alias showSceneLight: sceneLight.visible property alias showGrid: helperGrid.visible + property alias gridColor: helperGrid.gridColor property alias sceneHelpers: sceneHelpers property alias perspectiveCamera: scenePerspectiveCamera property alias orthoCamera: sceneOrthoCamera diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml index 228154a9a49..a3f14e9d113 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml @@ -48,6 +48,7 @@ Item { property alias contentItem: contentItem property color backgroundGradientColorStart: "#222222" property color backgroundGradientColorEnd: "#999999" + property color gridColor: "#aaaaaa" enum SelectionMode { Item, Group } enum TransformMode { Move, Rotate, Scale } @@ -100,12 +101,14 @@ Item { {"usePerspective": usePerspective, "showSceneLight": showEditLight, "showGrid": showGrid, + "gridColor": gridColor, "importScene": activeScene, "cameraZoomFactor": cameraControl._zoomFactor, "z": 1}); editView.usePerspective = Qt.binding(function() {return usePerspective;}); editView.showSceneLight = Qt.binding(function() {return showEditLight;}); editView.showGrid = Qt.binding(function() {return showGrid;}); + editView.gridColor = Qt.binding(function() {return gridColor;}); editView.cameraZoomFactor = Qt.binding(function() {return cameraControl._zoomFactor;}); selectionBoxes.length = 0; @@ -211,10 +214,19 @@ Item { function updateViewStates(viewStates) { if ("selectBackgroundColor" in viewStates) { - var color = viewStates.selectBackgroundColor - backgroundGradientColorStart = color[0]; - backgroundGradientColorEnd = color[1]; + if (Array.isArray(viewStates.selectBackgroundColor)) { + var colors = viewStates.selectBackgroundColor + backgroundGradientColorStart = colors[0]; + backgroundGradientColorEnd = colors[1]; + } else { + var color = viewStates.selectBackgroundColor + backgroundGradientColorStart = color; + backgroundGradientColorEnd = color; + } } + + if ("selectGridColor" in viewStates) + viewRoot.gridColor = viewStates.selectGridColor } // If resetToDefault is true, tool states not specifically set to anything will be reset to diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/HelperGrid.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/HelperGrid.qml index 66f383518eb..ef10818eedc 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/HelperGrid.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/HelperGrid.qml @@ -33,6 +33,7 @@ Node { property alias lines: gridGeometry.lines property alias step: gridGeometry.step property alias subdivAlpha: subGridMaterial.opacity + property alias gridColor: mainGridMaterial.diffuseColor eulerRotation.x: 90 diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/SceneView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/SceneView3D.qml index e59392b1ee2..1d0e0377d3b 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/SceneView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/SceneView3D.qml @@ -32,6 +32,7 @@ View3D { property bool usePerspective: false property alias showSceneLight: sceneLight.visible property alias showGrid: helperGrid.visible + property alias gridColor: helperGrid.gridColor property alias sceneHelpers: sceneHelpers property alias perspectiveCamera: scenePerspectiveCamera property alias orthoCamera: sceneOrthoCamera diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index effa871ce6d..d614901ca97 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -1742,7 +1742,7 @@ QObject *Qt5InformationNodeInstanceServer::find3DSceneRoot(QObject *obj) const } void Qt5InformationNodeInstanceServer::setup3DEditView(const QList &instanceList, - const QHash &toolStates) + const CreateSceneCommand &command) { #ifdef QUICK3D_MODULE if (!m_editView3DData.rootItem) @@ -1775,6 +1775,7 @@ void Qt5InformationNodeInstanceServer::setup3DEditView(const QList &toolStates = command.edit3dToolStates; QString lastSceneId; auto helper = qobject_cast(m_3dHelper); if (helper) { @@ -1828,11 +1829,23 @@ void Qt5InformationNodeInstanceServer::setup3DEditView(const QListcomponentCompleted(createComponentCompletedCommand(instanceList)); if (ViewConfig::isQuick3DMode()) { - setup3DEditView(instanceList, command.edit3dToolStates); + setup3DEditView(instanceList, command); updateRotationBlocks(command.auxiliaryChanges); } @@ -1963,12 +1976,6 @@ void Qt5InformationNodeInstanceServer::createScene(const CreateSceneCommand &com #ifdef IMPORT_QUICK3D_ASSETS QTimer::singleShot(0, this, &Qt5InformationNodeInstanceServer::resolveImportSupport); #endif - - if (!command.edit3dBackgroundColor.isEmpty()) { - View3DActionCommand backgroundColorCommand(View3DActionCommand::SelectBackgroundColor, - QVariant::fromValue(command.edit3dBackgroundColor)); - view3DAction(backgroundColorCommand); - } } void Qt5InformationNodeInstanceServer::sendChildrenChangedCommand(const QList &childList) @@ -2233,9 +2240,12 @@ void Qt5InformationNodeInstanceServer::view3DAction(const View3DActionCommand &c case View3DActionCommand::ShowCameraFrustum: updatedToolState.insert("showCameraFrustum", command.isEnabled()); break; - case View3DActionCommand::SelectBackgroundColor: { + case View3DActionCommand::SelectBackgroundColor: updatedViewState.insert("selectBackgroundColor", command.value()); break; + case View3DActionCommand::SelectGridColor: { + updatedViewState.insert("selectGridColor", command.value()); + break; } #ifdef QUICK3D_PARTICLES_MODULE case View3DActionCommand::ShowParticleEmitter: diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h index 291b2c3eecd..9f6e4506908 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h @@ -114,7 +114,7 @@ private: void createEditView3D(); void create3DPreviewView(); void setup3DEditView(const QList &instanceList, - const QHash &toolStates); + const CreateSceneCommand &command); void createCameraAndLightGizmos(const QList &instanceList) const; void add3DViewPorts(const QList &instanceList); void add3DScenes(const QList &instanceList); diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 1ac2f477bf3..bd55391a314 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -158,6 +158,7 @@ extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/edit3d SOURCES edit3dview.cpp edit3dview.h + edit3dviewconfig.h edit3dwidget.cpp edit3dwidget.h edit3dcanvas.cpp edit3dcanvas.h edit3dactions.cpp edit3dactions.h diff --git a/src/plugins/qmldesigner/components/edit3d/backgroundcolorselection.cpp b/src/plugins/qmldesigner/components/edit3d/backgroundcolorselection.cpp index 76db41a177e..3d7a9207c08 100644 --- a/src/plugins/qmldesigner/components/edit3d/backgroundcolorselection.cpp +++ b/src/plugins/qmldesigner/components/edit3d/backgroundcolorselection.cpp @@ -23,85 +23,56 @@ ** ****************************************************************************/ -#include "backgroundcolorselection.h" +#pragma once + +#include -#include #include -#include -#include + +#include "backgroundcolorselection.h" +#include "edit3dviewconfig.h" using namespace QmlDesigner; -namespace { -QList readBackgroundColorConfiguration() -{ - QVariant var = QmlDesigner::DesignerSettings::getValue( - QmlDesigner::DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR); - - if (!var.isValid()) - return {}; - - auto colorNameList = var.value>(); - QTC_ASSERT(colorNameList.size() == 2, return {}); - - return {colorNameList[0], colorNameList[1]}; -} - -void setBackgroundColorConfiguration(const QList &colorConfig) -{ - auto view = QmlDesignerPlugin::instance()->viewManager().nodeInstanceView(); - View3DActionCommand cmd(View3DActionCommand::SelectBackgroundColor, - QVariant::fromValue(colorConfig)); - view->view3DAction(cmd); -} - -void saveBackgroundColorConfiguration(const QList &colorConfig) -{ - QList colorsSaved = {colorConfig[0].name(), colorConfig[1].name()}; - QmlDesigner::DesignerSettings::setValue( - QmlDesigner::DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR, - QVariant::fromValue(colorsSaved)); -} - -} // namespace - -QColorDialog *BackgroundColorSelection::createDialog(QWidget *parent) -{ - auto dialog = new QColorDialog(parent); - - dialog->setModal(true); - dialog->setAttribute(Qt::WA_DeleteOnClose); - - const QList oldColorConfig = readBackgroundColorConfiguration(); - - dialog->show(); - - QObject::connect(dialog, &QColorDialog::currentColorChanged, dialog, [](const QColor &color) { - setBackgroundColorConfiguration({color, color}); - }); - - QObject::connect(dialog, &QColorDialog::colorSelected, dialog, [](const QColor &color) { - saveBackgroundColorConfiguration({color, color}); - }); - - if (!oldColorConfig.isEmpty()) { - QObject::connect(dialog, &QColorDialog::rejected, dialog, [oldColorConfig]() { - setBackgroundColorConfiguration(oldColorConfig); - }); - } - - return dialog; -} - -void BackgroundColorSelection::showBackgroundColorSelectionWidget(QWidget *parent) +void BackgroundColorSelection::showBackgroundColorSelectionWidget(QWidget *parent, const QByteArray &key, + View3DActionCommand::Type cmdType) { if (m_dialog) return; - m_dialog = BackgroundColorSelection::createDialog(parent); + m_dialog = BackgroundColorSelection::createColorDialog(parent, key, cmdType); QTC_ASSERT(m_dialog, return); QObject::connect(m_dialog, &QWidget::destroyed, m_dialog, [&]() { m_dialog = nullptr; }); } + +QColorDialog *BackgroundColorSelection::createColorDialog(QWidget *parent, const QByteArray &key, + View3DActionCommand::Type cmdType) +{ + auto dialog = new QColorDialog(parent); + + dialog->setModal(true); + dialog->setAttribute(Qt::WA_DeleteOnClose); + + QList oldColorConfig = Edit3DViewConfig::load(key); + + dialog->show(); + + QObject::connect(dialog, &QColorDialog::currentColorChanged, dialog, [cmdType](const QColor &color) { + Edit3DViewConfig::set(cmdType, color); + }); + + QObject::connect(dialog, &QColorDialog::colorSelected, dialog, [key](const QColor &color) { + Edit3DViewConfig::save(key, color); + }); + + if (Edit3DViewConfig::isValid(oldColorConfig)) { + QObject::connect(dialog, &QColorDialog::rejected, dialog, [cmdType, oldColorConfig]() { + Edit3DViewConfig::set(cmdType, oldColorConfig); + }); + } + + return dialog; +} diff --git a/src/plugins/qmldesigner/components/edit3d/backgroundcolorselection.h b/src/plugins/qmldesigner/components/edit3d/backgroundcolorselection.h index d8832f40fda..f7df55b8e30 100644 --- a/src/plugins/qmldesigner/components/edit3d/backgroundcolorselection.h +++ b/src/plugins/qmldesigner/components/edit3d/backgroundcolorselection.h @@ -25,9 +25,13 @@ #pragma once -#include +#include +#include + +QT_FORWARD_DECLARE_CLASS(QColorDialog) namespace QmlDesigner { + class BackgroundColorSelection : public QObject { Q_OBJECT @@ -37,10 +41,13 @@ public: : QObject{parent} {} - static void showBackgroundColorSelectionWidget(QWidget *parent); + static void showBackgroundColorSelectionWidget(QWidget *parent, const QByteArray &key, + View3DActionCommand::Type cmdType); private: - static QColorDialog *createDialog(QWidget *parent); + static QColorDialog *createColorDialog(QWidget *parent, const QByteArray &key, + View3DActionCommand::Type cmdType); + inline static QColorDialog *m_dialog = nullptr; }; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp index 76343584c9c..e90f20767d1 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp @@ -48,7 +48,8 @@ Edit3DActionTemplate::Edit3DActionTemplate(const QString &description, void Edit3DActionTemplate::actionTriggered(bool b) { - if (m_type != View3DActionCommand::Empty && m_type != View3DActionCommand::SelectBackgroundColor) { + if (m_type != View3DActionCommand::Empty && m_type != View3DActionCommand::SelectBackgroundColor + && m_type != View3DActionCommand::SelectGridColor) { auto view = QmlDesignerPlugin::instance()->viewManager().nodeInstanceView(); View3DActionCommand cmd(m_type, b); view->view3DAction(cmd); diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index 1ef3055ada6..aa76d58370d 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -28,6 +28,8 @@ #include "edit3dcanvas.h" #include "edit3dview.h" #include "edit3dwidget.h" +#include "edit3dviewconfig.h" +#include "backgroundcolorselection.h" #include #include @@ -42,8 +44,6 @@ #include #include -#include - #include #include @@ -266,6 +266,70 @@ void Edit3DView::setSeeker(SeekerSlider *slider) m_seeker = slider; } +Edit3DAction *Edit3DView::createSelectBackgrounColorAction() +{ + QString description = QCoreApplication::translate("SelectBackgroundColorAction", + "Select Background Color"); + QString tooltip = QCoreApplication::translate("SelectBackgroundColorAction", + "Select a color for the background of the 3D Editor."); + + auto operation = [this](const SelectionContext &) { + BackgroundColorSelection::showBackgroundColorSelectionWidget( + edit3DWidget(), + DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR, + View3DActionCommand::SelectBackgroundColor); + }; + + return new Edit3DAction( + Constants::EDIT3D_EDIT_SELECT_BACKGROUND_COLOR, View3DActionCommand::SelectBackgroundColor, + description, + {}, false, false, {}, {}, operation, + tooltip); +} + +Edit3DAction *Edit3DView::createGridColorSelectionAction() +{ + QString description = QCoreApplication::translate("SelectGridColorAction", "Select Grid Color"); + QString tooltip = QCoreApplication::translate("SelectGridColorAction", + "Select a color for the grid lines of the 3D Editor."); + + auto operation = [this](const SelectionContext &) { + BackgroundColorSelection::showBackgroundColorSelectionWidget( + edit3DWidget(), + DesignerSettingsKey::EDIT3DVIEW_GRID_COLOR, + View3DActionCommand::SelectGridColor); + }; + + return new Edit3DAction( + Constants::EDIT3D_EDIT_SELECT_GRID_COLOR, View3DActionCommand::SelectGridColor, + description, {}, false, false, {}, {}, operation, + tooltip); +} + +Edit3DAction *Edit3DView::createResetColorAction() +{ + QString description = QCoreApplication::translate("ResetEdit3DColorsAction", "Reset Colors"); + QString tooltip = QCoreApplication::translate("ResetEdit3DColorsAction", + "Reset the background color and the color of the " + "grid lines of the 3D Editor to the default valus."); + + auto operation = [](const SelectionContext &) { + QList bgColors = {QRgb(0x222222), QRgb(0x999999)}; + Edit3DViewConfig::set(View3DActionCommand::SelectBackgroundColor, bgColors); + Edit3DViewConfig::save(DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR, bgColors); + + QColor gridColor{0xaaaaaa}; + Edit3DViewConfig::set(View3DActionCommand::SelectGridColor, gridColor); + Edit3DViewConfig::save(DesignerSettingsKey::EDIT3DVIEW_GRID_COLOR, gridColor); + }; + + return new Edit3DAction( + QmlDesigner::Constants::EDIT3D_EDIT_RESET_BACKGROUND_COLOR, View3DActionCommand::ResetBackgroundColor, + description, + {}, false, false, {}, {}, operation, + tooltip); +} + void Edit3DView::createEdit3DActions() { m_selectionModeAction @@ -338,32 +402,6 @@ void Edit3DView::createEdit3DActions() QKeySequence(Qt::Key_G), true, true, {}, {}, nullptr, QCoreApplication::translate("ShowGridAction", "Toggle the visibility of the helper grid.")); - SelectionContextOperation showBackgroundColorSelection = [this](const SelectionContext &) { - BackgroundColorSelection::showBackgroundColorSelectionWidget(edit3DWidget()); - }; - - m_backgroundColorSelectionAction = new Edit3DAction( - QmlDesigner::Constants::EDIT3D_EDIT_SELECT_BACKGROUND_COLOR, View3DActionCommand::SelectBackgroundColor, - QCoreApplication::translate("SelectBackgroundColorAction", "Select Background Color"), - {}, false, false, {}, {}, showBackgroundColorSelection, - QCoreApplication::translate("SelectBackgroundColorAction", "Select a color for the background of the 3D Editor.")); - - m_resetBackgroundColorAction = new Edit3DAction( - QmlDesigner::Constants::EDIT3D_EDIT_RESET_BACKGROUND_COLOR, View3DActionCommand::ResetBackgroundColor, - QCoreApplication::translate("ResetBackgroundColorAction", "Reset Background Color"), - {}, false, false, {}, {}, [](const SelectionContext &) { - QList colors = {QRgb(0x222222), QRgb(0x999999)}; - auto view = QmlDesignerPlugin::instance()->viewManager().nodeInstanceView(); - View3DActionCommand cmd(View3DActionCommand::SelectBackgroundColor, QVariant::fromValue(colors)); - view->view3DAction(cmd); - - QList colorsToSave = {colors[0].name(), colors[1].name()}; - QmlDesigner::DesignerSettings::setValue( - QmlDesigner::DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR, - QVariant::fromValue(colorsToSave)); - }, - QCoreApplication::translate("ResetBackgroundColorAction", "Reset background color of the 3D Editor to the default value.")); - m_showSelectionBoxAction = new Edit3DAction( QmlDesigner::Constants::EDIT3D_EDIT_SHOW_SELECTION_BOX, View3DActionCommand::ShowSelectionBox, QCoreApplication::translate("ShowSelectionBoxAction", "Show Selection Boxes"), @@ -521,8 +559,9 @@ void Edit3DView::createEdit3DActions() m_visibilityToggleActions << m_showCameraFrustumAction; m_visibilityToggleActions << m_showParticleEmitterAction; - m_backgroundColorActions << m_backgroundColorSelectionAction; - m_backgroundColorActions << m_resetBackgroundColorAction; + m_backgroundColorActions << createSelectBackgrounColorAction(); + m_backgroundColorActions << createGridColorSelectionAction(); + m_backgroundColorActions << createResetColorAction(); } QVector Edit3DView::leftActions() const diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index e5cb2aba51b..b817c20d1a4 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -85,6 +85,10 @@ private: void createEdit3DWidget(); void checkImports(); + Edit3DAction *createSelectBackgrounColorAction(); + Edit3DAction *createGridColorSelectionAction(); + Edit3DAction *createResetColorAction(); + QPointer m_edit3DWidget; QVector m_leftActions; QVector m_rightActions; @@ -101,8 +105,6 @@ private: Edit3DAction *m_orientationModeAction = nullptr; Edit3DAction *m_editLightAction = nullptr; Edit3DAction *m_showGridAction = nullptr; - Edit3DAction *m_backgroundColorSelectionAction = nullptr; - Edit3DAction *m_resetBackgroundColorAction = nullptr; Edit3DAction *m_showSelectionBoxAction = nullptr; Edit3DAction *m_showIconGizmoAction = nullptr; Edit3DAction *m_showCameraFrustumAction = nullptr; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dviewconfig.h b/src/plugins/qmldesigner/components/edit3d/edit3dviewconfig.h new file mode 100644 index 00000000000..8e4081176af --- /dev/null +++ b/src/plugins/qmldesigner/components/edit3d/edit3dviewconfig.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include +#include + +#include +#include + +namespace QmlDesigner { + +class Edit3DViewConfig +{ +public: + static QList load(const char key[]) + { + QVariant var = DesignerSettings::getValue(key); + + if (!var.isValid()) + return {}; + + auto colorNameList = var.value(); + + return Utils::transform(colorNameList, [](const QString &colorName) { + return QColor{colorName}; + }); + } + + static void set(View3DActionCommand::Type type, const QList &colorConfig) + { + if (colorConfig.size() == 1) + set(type, colorConfig.at(0)); + else + setVariant(type, QVariant::fromValue(colorConfig)); + } + + static void set(View3DActionCommand::Type type, const QColor &color) + { + setVariant(type, QVariant::fromValue(color)); + } + + static void save(const QByteArray &key, const QList &colorConfig) + { + QStringList colorNames = Utils::transform(colorConfig, [](const QColor &color) { + return color.name(); + }); + + saveVariant(key, QVariant::fromValue(colorNames)); + } + + static void save(const QByteArray &key, const QColor &color) + { + saveVariant(key, QVariant::fromValue(color.name())); + } + + static bool isValid(const QList &colorConfig) { return !colorConfig.isEmpty(); } + +private: + static void setVariant(View3DActionCommand::Type type, const QVariant &colorConfig) + { + auto view = QmlDesignerPlugin::instance()->viewManager().nodeInstanceView(); + View3DActionCommand cmd(type, colorConfig); + view->view3DAction(cmd); + } + + static void saveVariant(const QByteArray &key, const QVariant &colorConfig) + { + DesignerSettings::setValue(key, colorConfig); + } +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 278d65fcd9c..196faf2848e 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -94,6 +94,8 @@ #include +#include + #include #include @@ -983,17 +985,6 @@ QList filterNodesForSkipItems(const QList &nodeList) return filteredNodeList; } -QList readBackgroundColorConfiguration(const QVariant &var) -{ - if (!var.isValid()) - return {}; - - auto colorNameList = var.value>(); - QTC_ASSERT(colorNameList.size() == 2, return {}); - - return {colorNameList[0], colorNameList[1]}; -} - CreateSceneCommand NodeInstanceView::createCreateSceneCommand() { QList nodeList = allModelNodes(); @@ -1148,16 +1139,15 @@ CreateSceneCommand NodeInstanceView::createCreateSceneCommand() if (stateNode.isValid() && stateNode.metaInfo().isSubclassOf("QtQuick.State", 1, 0)) stateInstanceId = stateNode.internalId(); - QVariant value + QColor gridColor; + QList backgroundColor; + #ifndef QMLDESIGNER_TEST - = QmlDesigner::DesignerSettings::getValue( - QmlDesigner::DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR); -#else - = {}; + backgroundColor = Edit3DViewConfig::load(DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR); + QList gridColorList = Edit3DViewConfig::load(DesignerSettingsKey::EDIT3DVIEW_GRID_COLOR); + if (!gridColorList.isEmpty()) + gridColor = gridColorList.at(0); #endif - QList edit3dBackgroundColor; - if (value.isValid()) - edit3dBackgroundColor = readBackgroundColorConfiguration(value); return CreateSceneCommand( instanceContainerList, @@ -1180,7 +1170,8 @@ CreateSceneCommand NodeInstanceView::createCreateSceneCommand() m_captureImageMinimumSize, m_captureImageMaximumSize, stateInstanceId, - edit3dBackgroundColor); + backgroundColor, + gridColor); } ClearSceneCommand NodeInstanceView::createClearSceneCommand() const diff --git a/src/plugins/qmldesigner/designersettings.cpp b/src/plugins/qmldesigner/designersettings.cpp index 5474fd50d34..1b9651d471d 100644 --- a/src/plugins/qmldesigner/designersettings.cpp +++ b/src/plugins/qmldesigner/designersettings.cpp @@ -82,6 +82,7 @@ void DesignerSettings::fromSettings(QSettings *settings) restoreValue(settings, DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET, true); const QStringList defaultValue = QStringList() << "#222222" << "#999999"; restoreValue(settings, DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR, defaultValue); + restoreValue(settings, DesignerSettingsKey::EDIT3DVIEW_GRID_COLOR, "#aaaaaa"); restoreValue(settings, DesignerSettingsKey::SMOOTH_RENDERING, false); settings->endGroup(); diff --git a/src/plugins/qmldesigner/designersettings.h b/src/plugins/qmldesigner/designersettings.h index 4dbee7752ba..f39268b1869 100644 --- a/src/plugins/qmldesigner/designersettings.h +++ b/src/plugins/qmldesigner/designersettings.h @@ -50,6 +50,7 @@ const char WARNING_FOR_DESIGNER_FEATURES_IN_EDITOR[] = "WarnAboutQtQuickDesigner const char SHOW_DEBUGVIEW[] = "ShowQtQuickDesignerDebugView"; const char ENABLE_DEBUGVIEW[] = "EnableQtQuickDesignerDebugView"; const char EDIT3DVIEW_BACKGROUND_COLOR[] = "Edit3DViewBackgroundColor"; +const char EDIT3DVIEW_GRID_COLOR[] = "Edit3DViewGridLineColor"; const char ALWAYS_SAVE_IN_CRUMBLEBAR[] = "AlwaysSaveInCrumbleBar"; const char USE_DEFAULT_PUPPET[] = "UseDefaultQml2Puppet"; const char PUPPET_TOPLEVEL_BUILD_DIRECTORY[] = "PuppetToplevelBuildDirectory"; diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index 8dc2b947496..b2ed51cd874 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -66,6 +66,7 @@ const char EDIT3D_ORIENTATION[] = "QmlDesigner.Editor3D.OrientationToggle"; const char EDIT3D_EDIT_LIGHT[] = "QmlDesigner.Editor3D.EditLightToggle"; const char EDIT3D_EDIT_SHOW_GRID[] = "QmlDesigner.Editor3D.ToggleGrid"; const char EDIT3D_EDIT_SELECT_BACKGROUND_COLOR[] = "QmlDesigner.Editor3D.SelectBackgroundColor"; +const char EDIT3D_EDIT_SELECT_GRID_COLOR[] = "QmlDesigner.Editor3D.SelectGridColor"; const char EDIT3D_EDIT_RESET_BACKGROUND_COLOR[] = "QmlDesigner.Editor3D.ResetBackgroundColor"; const char EDIT3D_EDIT_SHOW_SELECTION_BOX[] = "QmlDesigner.Editor3D.ToggleSelectionBox"; const char EDIT3D_EDIT_SHOW_ICON_GIZMO[] = "QmlDesigner.Editor3D.ToggleIconGizmo"; diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 5266a146a3d..be482a22ca6 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -530,6 +530,7 @@ Project { "debugview/debugviewwidget.ui", "edit3d/edit3dview.cpp", "edit3d/edit3dview.h", + "edit3d/edit3dviewconfig.h", "edit3d/backgroundcolorselection.cpp", "edit3d/backgroundcolorselection.h", "edit3d/edit3dwidget.cpp", From 9c3636af497f8bdb2745bf85cc0286c9c6675987 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Mon, 13 Jun 2022 14:51:27 +0300 Subject: [PATCH 21/53] QmlDesigner: Ensure materials render properly after puppet reset This is a workaround for quick3d issue QTBUG-103316, where material library materials for editor are properly initialized only if the first encountered View3D in the scene is also rendered first. Fixes: QDS-7084 Change-Id: I8bb6a8e6bfe2fcffddfe86f92157d386fdf4095d Reviewed-by: Reviewed-by: Thomas Hartmann --- .../qt5informationnodeinstanceserver.cpp | 42 ++++++++++++++++++- .../qt5informationnodeinstanceserver.h | 1 + 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index d614901ca97..97a60b54059 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -848,6 +848,18 @@ void Qt5InformationNodeInstanceServer::updateActiveSceneToEditView3D(bool timerC m_activeSceneIdUpdateTimer.stop(); } + // We may have to substitute another scene to work around QTBUG-103316 + // The worked around issue is that if a material is used in multiple scenes, there is some + // kind of ownership for it in the first View3D that uses it, so if that view is not rendered + // first, the material will not be properly initialized for other views using it. + // To make materials work properly, we ensure that views are rendered at least once in the + // order they appear in the scene. + if (!m_priorityView3DsToRender.isEmpty()) { + QObject *sceneRoot = find3DSceneRoot(m_priorityView3DsToRender.first()); + if (sceneRoot) + activeSceneVar = objectToVariant(sceneRoot); + } + QMetaObject::invokeMethod(m_editView3DData.rootItem, "setActiveScene", Qt::QueuedConnection, Q_ARG(QVariant, activeSceneVar), Q_ARG(QVariant, QVariant::fromValue(sceneId))); @@ -1010,7 +1022,7 @@ void Qt5InformationNodeInstanceServer::doRender3DEditView() // If we have only one or no render queued, send the result to the creator side. // Otherwise, we'll hold on that until we have rendered all pending frames to ensure sent // results are correct. - if (m_need3DEditViewRender <= 1) { + if (m_priorityView3DsToRender.isEmpty() && m_need3DEditViewRender <= 1) { nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::Render3DView, QVariant::fromValue(imgContainer)}); #ifdef QUICK3D_PARTICLES_MODULE @@ -1021,6 +1033,25 @@ void Qt5InformationNodeInstanceServer::doRender3DEditView() #endif } + if (!m_priorityView3DsToRender.isEmpty()) { + static int tryCounter = 0; + QObject *sceneRoot = find3DSceneRoot(m_priorityView3DsToRender.first()); + bool needAnotherRender = false; + if (sceneRoot) { + // Active scene is updated asynchronously, so verify we are actually rendering + // the correct priority scene + QObject *activeScene = QQmlProperty::read(m_editView3DData.rootItem, "activeScene").value(); + needAnotherRender = activeScene != sceneRoot; + } + + if (!needAnotherRender || ++tryCounter > 10) { + m_priorityView3DsToRender.removeFirst(); + updateActiveSceneToEditView3D(); + tryCounter = 0; + } + ++m_need3DEditViewRender; + } + if (m_need3DEditViewRender > 0) { // We queue another render even if the requested render count was one, because another // render is needed to ensure gizmo geometries are properly updated. @@ -1057,6 +1088,13 @@ void Qt5InformationNodeInstanceServer::doRenderModelNodeImageView() { // This crashes on Qt 6.0.x due to QtQuick3D issue, so the preview generation is disabled #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) || QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) + if (!m_priorityView3DsToRender.isEmpty()) { + // Postpone any preview renders until we have rendered the priority views to ensure + // materials in material library are properly initialized + m_renderModelNodeImageViewTimer.start(17); + return; + } + RequestModelNodePreviewImageCommand cmd = *m_modelNodePreviewImageCommands.begin(); ServerNodeInstance instance; if (cmd.renderItemId() >= 0) @@ -1574,6 +1612,8 @@ void Qt5InformationNodeInstanceServer::add3DViewPorts(const QList m_view3Ds; QMultiHash m_3DSceneMap; // key: scene root, value: node QObject *m_active3DView = nullptr; + QList m_priorityView3DsToRender; QObject *m_active3DScene = nullptr; QSet m_parentChangedSet; QList m_completedComponentList; From 221376aa3823df2d36dedaf2e1ab97231c0ffbde Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 16 Jun 2022 15:30:25 +0300 Subject: [PATCH 22/53] QmlDesigner: Reset puppet when material is added This is workaround for material rendering issues in quick3d when a material is shared by multiple windows. Fixes: QDS-7096 Fixes: QDS-7118 Change-Id: I64fe2e51c5dda8e238502e5d926b6938b7b0731a Reviewed-by: Thomas Hartmann --- .../materialbrowser/materialbrowsermodel.cpp | 5 ++++ .../materialbrowser/materialbrowsermodel.h | 1 + .../materialbrowser/materialbrowserview.cpp | 26 ++++++++++++++----- .../materialbrowser/materialbrowserview.h | 2 +- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp index d0420163c3a..49f313534d1 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp @@ -120,6 +120,11 @@ void MaterialBrowserModel::setHasModelSelection(bool b) emit hasModelSelectionChanged(); } +QList MaterialBrowserModel::materials() const +{ + return m_materialList; +} + void MaterialBrowserModel::setSearchText(const QString &searchText) { QString lowerSearchText = searchText.toLower(); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h index 32db357caeb..73e6b2f99c6 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h @@ -58,6 +58,7 @@ public: bool hasModelSelection() const; void setHasModelSelection(bool b); + QList materials() const; void setMaterials(const QList &materials, bool hasQuick3DImport); void removeMaterial(const ModelNode &material); void updateMaterialName(const ModelNode &material); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 9d6072856e9..1af67d4fef7 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -97,10 +97,12 @@ void MaterialBrowserView::modelAttached(Model *model) m_widget->clearSearchFilter(); m_hasQuick3DImport = model->hasImport("QtQuick3D"); - QTimer::singleShot(0, this, &MaterialBrowserView::refreshModel); + QTimer::singleShot(0, this, [this]() { + refreshModel(true); + }); } -void MaterialBrowserView::refreshModel() +void MaterialBrowserView::refreshModel(bool updateImages) { ModelNode matLib = modelNodeForId(Constants::MATERIAL_LIB_ID); QList materials; @@ -115,8 +117,10 @@ void MaterialBrowserView::refreshModel() m_widget->materialBrowserModel()->setMaterials(materials, m_hasQuick3DImport); - for (const ModelNode &node : std::as_const(materials)) - model()->nodeInstanceView()->previewImageDataForGenericNode(node, {}); + if (updateImages) { + for (const ModelNode &node : std::as_const(materials)) + model()->nodeInstanceView()->previewImageDataForGenericNode(node, {}); + } } bool MaterialBrowserView::isMaterial(const ModelNode &node) const @@ -204,8 +208,9 @@ void MaterialBrowserView::nodeReparented(const ModelNode &node, bool matRemoved = oldParentNode.isValid() && oldParentNode.id() == Constants::MATERIAL_LIB_ID; if (matAdded || matRemoved) { - refreshModel(); - + if (matAdded) // Workaround to fix various material issues all likely caused by QTBUG-103316 + resetPuppet(); + refreshModel(!matAdded); int idx = m_widget->materialBrowserModel()->materialIndex(node); m_widget->materialBrowserModel()->selectMaterial(idx); } @@ -252,7 +257,7 @@ void MaterialBrowserView::importsChanged(const QList &addedImports, cons return; m_hasQuick3DImport = hasQuick3DImport; - refreshModel(); + refreshModel(true); } void MaterialBrowserView::customNotification(const AbstractView *view, const QString &identifier, @@ -267,6 +272,13 @@ void MaterialBrowserView::customNotification(const AbstractView *view, const QSt int idx = m_widget->materialBrowserModel()->materialIndex(nodeList.first()); if (idx != -1) m_widget->materialBrowserModel()->selectMaterial(idx); + } else if (identifier == "reset QmlPuppet") { + // Little delay is needed to allow puppet reset to actually be done, as it is async as well + QTimer::singleShot(200, this, [this]() { + const QList materials = m_widget->materialBrowserModel()->materials(); + for (const ModelNode &node : materials) + model()->nodeInstanceView()->previewImageDataForGenericNode(node, {}); + }); } } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h index e140eede136..c637efa49e1 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -62,7 +62,7 @@ public: const QList &nodeList, const QList &data) override; private: - void refreshModel(); + void refreshModel(bool updateImages); bool isMaterial(const ModelNode &node) const; QPointer m_widget; From 568004e12182458b44222cc53a545611992eba02 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Tue, 7 Jun 2022 21:19:44 +0300 Subject: [PATCH 23/53] QmlDesigner: Detect asset drag'n'drop support based on property type Handle property support for asset drag based on property type rather than name in order to support properties with any name (like lightProbe). Change-Id: I20fd422119db96aebd505b47888c97f0d94bf7f9 Reviewed-by: Reviewed-by: Miikka Heikkinen Reviewed-by: Thomas Hartmann --- .../components/materialeditor/materialeditorview.cpp | 7 ++++++- .../components/propertyeditor/propertyeditorvalue.cpp | 5 +++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index 28c17d75611..ca76d459660 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -767,11 +767,16 @@ void MaterialEditorView::customNotification(const AbstractView *view, const QStr void QmlDesigner::MaterialEditorView::highlightSupportedProperties(bool highlight) { + if (!m_selectedMaterial.isValid()) + return; + DesignerPropertyMap &propMap = m_qmlBackEnd->backendValuesPropertyMap(); const QStringList propNames = propMap.keys(); + NodeMetaInfo metaInfo = m_selectedMaterial.metaInfo(); + QTC_ASSERT(metaInfo.isValid(), return); for (const QString &propName : propNames) { - if (propName.endsWith("Map")) { + if (metaInfo.propertyTypeName(propName.toLatin1()) == "QtQuick3D.Texture") { QObject *propEditorValObj = propMap.value(propName).value(); PropertyEditorValue *propEditorVal = qobject_cast(propEditorValObj); propEditorVal->setHasActiveDrag(highlight); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp index 867fd9bd65b..fd568911abc 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp @@ -518,14 +518,15 @@ bool PropertyEditorValue::idListReplace(int idx, const QString &value) void PropertyEditorValue::commitDrop(const QString &path) { - if (m_modelNode.isSubclassOf("QtQuick3D.Material") && nameAsQString().endsWith("Map")) { + if (m_modelNode.isSubclassOf("QtQuick3D.Material") + && m_modelNode.metaInfo().propertyTypeName(m_name) == "QtQuick3D.Texture") { // create a texture node QmlDesigner::NodeMetaInfo metaInfo = m_modelNode.view()->model()->metaInfo("QtQuick3D.Texture"); QmlDesigner::ModelNode texture = m_modelNode.view()->createModelNode("QtQuick3D.Texture", metaInfo.majorVersion(), metaInfo.minorVersion()); texture.validId(); - modelNode().view()->rootModelNode().defaultNodeListProperty().reparentHere(texture); + m_modelNode.view()->rootModelNode().defaultNodeListProperty().reparentHere(texture); // TODO: group textures under 1 node (just like materials) // set texture source From d080e6331f3a75a28d2d3dd27ace853e7c51c9c0 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Wed, 15 Jun 2022 16:54:31 +0300 Subject: [PATCH 24/53] QmlDesigner: Parse material library on model attach Parsing has to happen so material in an old project are correctly appearing in the material views. Using a timer to wait until it is ok to create the material editor node. Otherwise errors happen. Change-Id: I54b532211f8a865c5183fab0fd8c12e5f15b983a Reviewed-by: Reviewed-by: Miikka Heikkinen Reviewed-by: Thomas Hartmann --- .../materialeditor/materialeditorview.cpp | 14 ++++++++++++-- .../components/materialeditor/materialeditorview.h | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index ca76d459660..10cd12a7c7b 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -73,6 +73,13 @@ MaterialEditorView::MaterialEditorView(QWidget *parent) m_updateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F7), m_stackedWidget); connect(m_updateShortcut, &QShortcut::activated, this, &MaterialEditorView::reloadQml); + m_ensureMatLibTimer.callOnTimeout([this] { + if (model() && model()->rewriterView() && !model()->rewriterView()->hasIncompleteTypeInformation()) { + materialLibraryNode(); // create the material library node + m_ensureMatLibTimer.stop(); + } + }); + m_stackedWidget->setStyleSheet(Theme::replaceCssColors( QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css")))); m_stackedWidget->setMinimumWidth(250); @@ -524,6 +531,11 @@ void MaterialEditorView::modelAttached(Model *model) m_hasQuick3DImport = model->hasImport("QtQuick3D"); + // Creating the material library node on model attach causes errors as long as the type information + // not complete yet, so we keep checking until type info is complete. + if (m_hasQuick3DImport) + m_ensureMatLibTimer.start(500); + if (!m_setupCompleted) { reloadQml(); m_setupCompleted = true; @@ -537,8 +549,6 @@ void MaterialEditorView::modelAboutToBeDetached(Model *model) { AbstractView::modelAboutToBeDetached(model); m_qmlBackEnd->materialEditorTransaction()->end(); - - } void MaterialEditorView::propertiesRemoved(const QList &propertyList) diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h index d9c9e4c1ba1..ff734ed30b8 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h @@ -114,6 +114,7 @@ private: bool noValidSelection() const; ModelNode m_selectedMaterial; + QTimer m_ensureMatLibTimer; QShortcut *m_updateShortcut = nullptr; int m_timerId = 0; QStackedWidget *m_stackedWidget = nullptr; From 74a7e9f00caf6d4a49081ef0081bc99fa47377c5 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Fri, 17 Jun 2022 13:45:52 +0200 Subject: [PATCH 25/53] QmlDesigner: Fix test build Amends 50aadacb6e670. Change-Id: If6cd7a1fc4df380693f3c4deee8791ed9863672a Reviewed-by: Miikka Heikkinen --- .../qmldesigner/designercore/instances/nodeinstanceview.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 196faf2848e..3498e749266 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -90,12 +90,11 @@ #include #include #include +#include #endif #include -#include - #include #include From db8dbe25e0528d5e845be5c5f1ca44ec0a923f14 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Thu, 24 Feb 2022 16:55:19 +0100 Subject: [PATCH 26/53] QmlDesigner: Add a wizard for a ui.qml .qml file pair Task-number: QDS-5963 Change-Id: I16ee8361779f11ca2f24f35877869f3e83279d10 Reviewed-by: Thomas Hartmann --- .../files/qtuiquickform/file.qml.tpl | 5 + .../files/qtuiquickform/fileForm.ui.qml.tpl | 33 +++++ .../files/qtuiquickform/file_ui.png | Bin 0 -> 1084 bytes .../files/qtuiquickform/file_ui@2.png | Bin 0 -> 1879 bytes .../files/qtuiquickform/wizard.json | 132 ++++++++++++++++++ 5 files changed, 170 insertions(+) create mode 100644 share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/file.qml.tpl create mode 100644 share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/fileForm.ui.qml.tpl create mode 100644 share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/file_ui.png create mode 100644 share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/file_ui@2.png create mode 100644 share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/wizard.json diff --git a/share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/file.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/file.qml.tpl new file mode 100644 index 00000000000..7bd94416c44 --- /dev/null +++ b/share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/file.qml.tpl @@ -0,0 +1,5 @@ +import QtQuick 2.15 + +%{FormClass} { + button.onClicked: console.log("Button Pressed") +} diff --git a/share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/fileForm.ui.qml.tpl b/share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/fileForm.ui.qml.tpl new file mode 100644 index 00000000000..1fcbbe97566 --- /dev/null +++ b/share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/fileForm.ui.qml.tpl @@ -0,0 +1,33 @@ +/* +This is a UI file (.ui.qml) that is intended to be edited in Qt Design Studio only. +It is supposed to be strictly declarative and only uses a subset of QML. If you edit +this file manually, you might introduce QML code that is not supported by Qt Design Studio. +Check out https://doc.qt.io/qtcreator/creator-quick-ui-forms.html for details on .ui.qml files. +*/ + +import QtQuick 2.15 +@if %{UseQtQuickControls2} +import QtQuick.Controls 2.15 +@endif +@if %{UseImport} +import %{ApplicationImport} +@endif + +%{RootItem} { +@if %{UseImport} + width: Constants.width + height: Constants.height +@else + width: 1024 + height: 768 +@endif + + property alias button: button + + Button { + id: button + x: 64 + y: 64 + text: qsTr("Button") + } +} diff --git a/share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/file_ui.png b/share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/file_ui.png new file mode 100644 index 0000000000000000000000000000000000000000..473a8430fe5642bbcd20f571c3815d8eb7e3fd66 GIT binary patch literal 1084 zcmeAS@N?(olHy`uVBq!ia0y~yU}$7uU`XI#28pCSbY);*YzXiPab;j&C@(Lc4+d}s zm`7!z91M^R1q&dohH#PH02hG>!nqJbN=ix~3^K^XganALn5d}e$jF$esD$|V174rA35=B_$?8q{2f(Q<9VC&z~O_5|WydGJpR3;NalU;NY;3 zknr&Eyu7@Ef`a0rqT=G>l9H0r($cch(z3F$^78V!y1MS}?!Lahi4!JFnlx$7oH=vn z&Yd@J-u(IV&%5eoGB7Y?l?3?(GcYnTF>`S7@(Tz`NGqu68W@>aSlW4c2ZTk(CnTk1 z6cm<}R#rDOHZ`}lwzYS3cK1%0GIjclnRAyeTe)iWx@|l595{6N$k_{*Z#;PN^x3=j zA3lBm{^RE_5cmxOfByXW^Y`!HfB*i?^_$bgz`(@l>EalYaqsQTo8gxoL>fM>*HDUb z4Y(s7z@fKeVUW2?m?_th*aJ%yn!1?GjNaX;zsr7firD$g^PbPT=DvUXOm%( z)}`zA^7fwXVhDGNb$qocP%NcqlS1+oR>{RWCnQoGmj{Z?F+bIG*)6i*k_NX@!z2a% z1fRESc>7Q5hlRa(`sc)%O$9}2zYWf9 zG8W!h#PaZQdc5kreS0fJrYTm~{&Iel_wwgzBbLYWRsZet(VeDq;%uG2!-g;KoKH5j zMtUxK(UgWvn?x16$h<9^pK=mPhK+^IV?^S^ID%BdArV9(Li%kbB+Aly|! zDTup3`Qi49oO2F<&_C6K3J$R*-x*A9iQHlGa4!}=;5WO8VS(1KKXR-V3Bd}P4Xf*q z$uh`>vF>J&S~gw&h*m?~>ZW3bH{32RDGIzBGS93^Si;tD%XY2$tnL4!#N@;TcfPBP zGd-Yq{%_gO4ZO8U(FS+J=9X{&H9IL*p=kNujcf^bul>E7 zUl+f7hxMxkQ}-4>`0cEJ_dm0G0h9U)frHCcW@*JvsIJoQocH9~jmX);A(I}6ykfY* zxPm!=J%j7q0-YTbg2Z%~1K1bvImj+hesP9*1&7{R_wWru4OR@L3?QZUjO?F<7Mk;X SjAUS7VDNPHb6Mw<&;$S}j|k@g literal 0 HcmV?d00001 diff --git a/share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/file_ui@2.png b/share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/file_ui@2.png new file mode 100644 index 0000000000000000000000000000000000000000..9cf67e875bdc95b48100b8a1ed489e75749d9f94 GIT binary patch literal 1879 zcmeAS@N?(olHy`uVBq!ia0y~yU}R!oU^u|R%)r2K7?A#>quWColK(NYcuaQ6K9^Wh5M42Ue8gp0!&1qB5VS+HSHk3&Qt zBwQu3`4A~mNr+LUrKNC<>FMckHbgciCI-TQTNfN092y)P77`K`8X6uN8W9#25gr~H z9v&GH5fu>;9TgQF9UT)D6%!pD6B82~9UT`F6CWEJ9~YOHkdU01n39x~nw*@Lnwp-L zmY$xTk)EEJk&&61nU$HDot2f7ot=}Dlbe&1mz$fHmzSTPUyz?)P*6}SJ7L0vDU&BpojP^e)Tz^_PoFVk z#;jSh=FFKhckbMI^XAQ;Kc9i=)?o$)hPIL*zhDMNMkZz!Ha0GPacLPj1w~~QHFYg* zU3~)+QwvLL8%Jk1cTYdRfZ&kO(6FfJn1sZXjLh7E;?nYp>bi!;=8o>Z{{9IQCrzF@ zZTj@tbLK5rv}noF<;z#BUb|uArcK+n@7sUi=&|D`PMtY-<;I-{kDfex{`&3v4<9~$ z{POktkDtGO|Nism&)1i=W_1srdfJXj9Llr$ZzzQ|d_n!xdgHGqSO#et)V#egG@L#RRF z#qQIr>KuX$3NLowWmV@874R3(Q<#_VeEUL6ojD2g?uU@l?B^Q6ar#i7A*6sE#JO@G3e*_o>jjzm70`a9oc(G{KBt)nO{p|RanBO{4(Zain_lx zY`T)7#maTZoE=_AzsgWyiC=mDvD1h4psj)ecS2sx-y!g4`ITM{j=Hc{|91$y$iLEm zQQ(N1gSqLr~FK*jNxpXT|L)%?ca#Y z-}4PV{=WXi7^0X-XaYlTLV0Cv)a)dmzmBXtj#mD&AC>;iPy`u;WGXt7)z3gFzo{{Z zb5Ey!)bELPE=3&2W~-`R`)jo~ie($luR>>;tt93TzZom%g*08g$J*$xQ`#C{uQN3vp4&Ke&C+>~1=mdWTCjVj=3D0bT=Om^aqjWA zeZJT2=_aLMJN@YoWcwGKT{b5#I(+>crRfRI#rbLLPp!#1zM$uJ$kQUulKDMz=T1u# z(VllUV^{V;7WtIexvvd>UjBOWu7A_F z<`bOfnRI74@bijFENB(Zc`KxJA+fuJY45t?1rEPWqih=QUC?(}kg}Ie;X+lJ_Wabf zY40veuHc0{ zD|%m9f1kyl!+NNZg%qq!$Gr-)%vN@FDfo3ZJ-GfpdFpLJfev1TdPirkhn!xrB}kdY z;0H zg&dS;+&{ypj+|HQm>gL0e{(z#sQJ;t?Ds&w!AU@Y!AU@+!BIei!BIe`!C}LWrObW; tY6|leLL6)ycQlkV*|4+-KT-a`Fu94}{9$XcGy?+zgQu&X%Q~loCICI9PY(b9 literal 0 HcmV?d00001 diff --git a/share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/wizard.json b/share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/wizard.json new file mode 100644 index 00000000000..26c628f4b31 --- /dev/null +++ b/share/qtcreator/qmldesigner/studio_templates/files/qtuiquickform/wizard.json @@ -0,0 +1,132 @@ +{ + "version": 1, + "supportedProjectTypes": [ ], + "id": "Q.QtStudio.QmlUIForm.2", + "category": "B.StudioQtQuickFiles", + "trDescription": "Creates a UI file (.ui.qml) along with a matching QML file for implementation purposes.", + "trDisplayName": "QtQuick UI Form", + "trDisplayCategory": "Qt Quick Files", + "icon": "file_ui.png", + "platformIndependent": true, + + "enabled": "%{JS: value('Plugins').indexOf('QmlJSEditor') >= 0}", + "options" : [ + { "key": "QmlFile", "value": "%{Class}.%{JS: Util.preferredSuffix('text/x-qml')}" }, + { "key": "UiFile", "value": "%{FormClass}.%{JS: Util.preferredSuffix('application/x-qt.ui+qml')}" }, + { "key": "ApplicationImport", "value": "%{QmlProjectName} 1.0" }, + { "key": "RootItem", "value": "%{JS: %{RootItemCB}.RootItem}" }, + { "key": "UseImportDefault", "value": "%{JS: false}" }, + { "key": "UseQtQuickControls2Default", "value": "%{JS: true}" } + ], + "pages" : + [ + { + "trDisplayName": "Define Class", + "trShortTitle": "Details", + "typeId": "Fields", + "data" : + [ + { + "name": "Class", + "trDisplayName": "Component name:", + "mandatory": true, + "type": "LineEdit", + "data": { + "validator": "(?:[A-Z_][a-zA-Z_0-9]*|)", + "fixup": "%{JS: '%{INPUT}'.charAt(0).toUpperCase() + '%{INPUT}'.slice(1) }" + } + }, + { + "name": "Sp1", + "type": "Spacer", + "data": { "factor": 2 } + }, + { + "name": "FormClass", + "trDisplayName": "Component form name:", + "mandatory": true, + "type": "LineEdit", + "data": { + "validator": "(?:[A-Z_][a-zA-Z_0-9]*|)", + "fixup": "%{JS: '%{INPUT}'.charAt(0).toUpperCase() + '%{INPUT}'.slice(1) }", + "trText": "%{Class}Form" + } + }, + { + "name": "TargetPath", + "type": "PathChooser", + "trDisplayName": "Path:", + "mandatory": true, + "data": + { + "kind": "directory", + "basePath": "%{InitialPath}", + "path": "%{InitialPath}" + } + }, + { + "name": "RootItemCB", + "trDisplayName": "Root Item:", + "type": "ComboBox", + "data": + { + "index": 0, + "items": + [ + { + "trKey": "Item", + "value": + "({ + 'RootItem': 'Item' + })" + }, + { + "trKey": "Rectangle", + "value": + "({ + 'RootItem': 'Rectangle' + })" + } + ] + } + }, + { + "name": "UseImport", + "trDisplayName": "Use Application Import", + "type": "CheckBox", + "data": + { + "checked": "%{UseImportDefault}" + } + }, + { + "name": "UseQtQuickControls2", + "trDisplayName": "Use QtQuick Controls 2", + "type": "CheckBox", + "data": + { + "checked": "%{UseQtQuickControls2Default}" + } + } + ] + } + ], + "generators" : + [ + { + "typeId": "File", + "data": [ + { + "source": "file.qml.tpl", + "target": "%{TargetPath}/%{QmlFile}", + "openInEditor": true + }, + { + "source": "fileForm.ui.qml.tpl", + "target": "%{TargetPath}/%{UiFile}", + "openInEditor": true + } + ] + } + ] +} From 2de2a4808ac3bfc17c6f51d03050afd7909ba120 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Tue, 21 Jun 2022 00:21:50 +0200 Subject: [PATCH 27/53] QmlDesigner: Fix FontComboBox drag'n'drop * Fix URL schema to also work under macOS * Avoid setting font name and further processing if error during font loading occurred Change-Id: I9d07a3daad8f7c8c464422b0cf3f94d61e4b231d Reviewed-by: Reviewed-by: Thomas Hartmann --- .../imports/HelperWidgets/FontComboBox.qml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/FontComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/FontComboBox.qml index 8c05fa2f878..2f087c3b299 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/FontComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/FontComboBox.qml @@ -65,9 +65,11 @@ StudioControls.ComboBox { onDropped: function(drop) { drop.accepted = root.hasActiveHoverDrag - var fontLoader = root.createFontLoader("file:///" + dropArea.assetPath) - root.backendValue.value = fontLoader.name - root.currentIndex = root.find(root.backendValue.value) + var fontLoader = root.createFontLoader("file:" + dropArea.assetPath) + if (fontLoader.status === FontLoader.Ready) { + root.backendValue.value = fontLoader.name + root.currentIndex = root.find(root.backendValue.value) + } root.hasActiveHoverDrag = false root.backendValue.commitDrop(dropArea.assetPath) } From 281c5348944a1a32aae2582a7712ac98a6b00061 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Tue, 21 Jun 2022 14:16:01 +0200 Subject: [PATCH 28/53] QmlDesigner: Make inErrorState() private This method should be only for internal use, since it is different from errors.isEmpty(), because the rewriter is only inErrorState if parsing/rewriting fails, not if there is a semantic error. Change-Id: Ib021e5e80750a4edcb4661755e85e6880fd6617c Reviewed-by: Miikka Heikkinen --- .../qmldesigner/assetexporterplugin/assetexporterview.cpp | 2 +- .../components/componentcore/designeractionmanager.cpp | 2 +- .../designercore/imagecache/imagecachecollector.cpp | 4 ++-- src/plugins/qmldesigner/designercore/include/rewriterview.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.cpp index 81dc1ce486c..a565b464450 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.cpp @@ -81,7 +81,7 @@ bool AssetExporterView::saveQmlFile(QString *error) const void AssetExporterView::modelAttached(Model *model) { - if (model->rewriterView() && model->rewriterView()->inErrorState()) + if (model->rewriterView() && !model->rewriterView()->errors().isEmpty()) setState(LoadState::QmlErrorState); AbstractView::modelAttached(model); diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index 7f5649ea5f6..a0bd7a23896 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -513,7 +513,7 @@ public: ->currentModel(); if (currentModel->rewriterView() - && currentModel->rewriterView()->inErrorState()) { + && !currentModel->rewriterView()->errors().isEmpty()) { throw DocumentError{}; } diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp index 429ca328afd..0a225d1ab09 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp @@ -96,11 +96,11 @@ void ImageCacheCollector::start(Utils::SmallStringView name, model->setRewriterView(&rewriterView); - bool is3DRoot = !rewriterView.inErrorState() + bool is3DRoot = rewriterView.errors().isEmpty() && (rewriterView.rootModelNode().isSubclassOf("QtQuick3D.Node") || rewriterView.rootModelNode().isSubclassOf("QtQuick3D.Material")); - if (rewriterView.inErrorState() || (!rewriterView.rootModelNode().metaInfo().isGraphicalItem() + if (!rewriterView.errors().isEmpty() || (!rewriterView.rootModelNode().metaInfo().isGraphicalItem() && !is3DRoot)) { if (abortCallback) abortCallback(ImageCache::AbortReason::Failed); diff --git a/src/plugins/qmldesigner/designercore/include/rewriterview.h b/src/plugins/qmldesigner/designercore/include/rewriterview.h index e3a4af0b758..4e099290cd0 100644 --- a/src/plugins/qmldesigner/designercore/include/rewriterview.h +++ b/src/plugins/qmldesigner/designercore/include/rewriterview.h @@ -127,7 +127,6 @@ public: void addError(const DocumentMessage &error); void enterErrorState(const QString &errorMessage); - bool inErrorState() const { return !m_rewritingErrorMessage.isEmpty(); } void leaveErrorState() { m_rewritingErrorMessage.clear(); } void resetToLastCorrectQml(); @@ -202,6 +201,7 @@ private: //variables void setupCanonicalHashes() const; void handleLibraryInfoUpdate(); void handleProjectUpdate(); + bool inErrorState() const { return !m_rewritingErrorMessage.isEmpty(); } TextModifier *m_textModifier = nullptr; int transactionLevel = 0; From 20bc8b8243ef24a4018096527ab2b0941767d056 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Mon, 13 Jun 2022 16:47:17 +0200 Subject: [PATCH 29/53] QmlDesigner: Install busy handler for every database access Even for pragma changes the statement can be getting busy. To prevent any exceptions in that case we set the busy handler directly after opening the database. Change-Id: Id10aab20d812d5224f8031fa895c396762858fdf Reviewed-by: Qt CI Bot Reviewed-by: Thomas Hartmann --- src/libs/sqlite/sqlitedatabase.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/sqlite/sqlitedatabase.cpp b/src/libs/sqlite/sqlitedatabase.cpp index 5e72d6660e3..96e6a87b8ac 100644 --- a/src/libs/sqlite/sqlitedatabase.cpp +++ b/src/libs/sqlite/sqlitedatabase.cpp @@ -93,12 +93,12 @@ void Database::activateLogging() void Database::open(LockingMode lockingMode) { m_databaseBackend.open(m_databaseFilePath, m_openMode); - m_databaseBackend.setLockingMode(lockingMode); - m_databaseBackend.setJournalMode(m_journalMode); if (m_busyTimeout > 0ms) m_databaseBackend.setBusyTimeout(m_busyTimeout); else m_databaseBackend.registerBusyHandler(); + m_databaseBackend.setLockingMode(lockingMode); + m_databaseBackend.setJournalMode(m_journalMode); registerTransactionStatements(); m_isOpen = true; } From 3dca62ac1e2f39bdc58dfd3f0f837cee4cb87d2c Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Mon, 13 Jun 2022 17:54:29 +0200 Subject: [PATCH 30/53] QmlDesigner: Fix crash for dangling target It can be that the target is a dangling pointer. Task-number: QDS-7134 Change-Id: I51b7c9109b0f9193c31ee645144e2a24d1244ba9 Reviewed-by: Reviewed-by: Thomas Hartmann --- src/plugins/qmldesigner/qmldesignerprojectmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp b/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp index 90e54ba65bb..366f1ef04e3 100644 --- a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp +++ b/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp @@ -141,7 +141,7 @@ public: ImageCacheCollectorNullImageHandling::DontCaptureNullImage}; PreviewTimeStampProvider timeStampProvider; AsynchronousImageFactory factory; - ::ProjectExplorer::Target *activeTarget = nullptr; + QPointer<::ProjectExplorer::Target> activeTarget; }; QmlDesignerProjectManager::QmlDesignerProjectManager() From 53642c9a2206efbe94898e682365a7adcf239994 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Tue, 21 Jun 2022 15:19:07 +0200 Subject: [PATCH 31/53] QmlDesigner: There can be also document messages from the puppet We have to check if the rewriterView()->errors() are actually empty. Change-Id: Icbcc82aa9229078fd51c975e48de3e1f06c29f4d Reviewed-by: Miikka Heikkinen --- .../qmldesigner/components/formeditor/formeditorview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp index 6c6e67be81e..dc499c03b9b 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp @@ -472,7 +472,7 @@ void FormEditorView::documentMessagesChanged(const QList &error if (!errors.isEmpty() && !model()->rewriterView()->hasIncompleteTypeInformation()) m_formEditorWidget->showErrorMessageBox(errors); - else + else if (rewriterView()->errors().isEmpty()) m_formEditorWidget->hideErrorMessageBox(); checkRootModelNode(); From 604342c3af64a423a9c60cae85d7dc5aafb8eb1b Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Fri, 17 Jun 2022 14:10:03 +0300 Subject: [PATCH 32/53] QmlDesigner: Add some delays to material browser refreshing Import change causes puppet to reset, so bit of delay is needed there before triggering material browser refresh. Also added similar delay to refresh done at model attach as it seems puppet reset can sometimes be triggered after attach as well. Fixes: QDS-7172 Change-Id: I47f7af54e55af074fb2b9e8cdb9e78fb051a16cc Reviewed-by: Mahmoud Badri Reviewed-by: Reviewed-by: Qt CI Bot --- .../materialbrowser/materialbrowserview.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 1af67d4fef7..c0e0f9ad3cb 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -97,7 +97,10 @@ void MaterialBrowserView::modelAttached(Model *model) m_widget->clearSearchFilter(); m_hasQuick3DImport = model->hasImport("QtQuick3D"); - QTimer::singleShot(0, this, [this]() { + + // Project load is already very busy and may even trigger puppet reset, so let's wait a moment + // before refreshing the model + QTimer::singleShot(1000, this, [this]() { refreshModel(true); }); } @@ -257,7 +260,17 @@ void MaterialBrowserView::importsChanged(const QList &addedImports, cons return; m_hasQuick3DImport = hasQuick3DImport; - refreshModel(true); + + if (m_hasQuick3DImport) { + // Import change will trigger puppet reset. + // However, it doesn't seem to trigger the notification about the reset, so wait here. + QTimer::singleShot(1000, this, [this]() { + refreshModel(true); + }); + } else { + // No quick3d import, so we can refresh immediately to clear the browser + refreshModel(true); + } } void MaterialBrowserView::customNotification(const AbstractView *view, const QString &identifier, From 62f34622df810ce0f036a08485630ececdc5f932 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 21 Jun 2022 11:14:22 +0300 Subject: [PATCH 33/53] QmlDesigner: Improve puppet reset handling in material browser Ensure just one reset call for cases where multiple materials are added at once, and detect reset completion from root instance completion notification instead of reset trigger notification, as it comes later and catches all puppet reset cases. Fixes: QDS-7119 Change-Id: I99baa718bd1c85b4581f07ce75193213110e8e96 Reviewed-by: Thomas Hartmann Reviewed-by: Reviewed-by: Mahmoud Badri --- .../materialbrowser/materialbrowserview.cpp | 40 ++++++++++--------- .../materialbrowser/materialbrowserview.h | 2 + 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index c0e0f9ad3cb..2a06d5acbc4 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -211,8 +211,11 @@ void MaterialBrowserView::nodeReparented(const ModelNode &node, bool matRemoved = oldParentNode.isValid() && oldParentNode.id() == Constants::MATERIAL_LIB_ID; if (matAdded || matRemoved) { - if (matAdded) // Workaround to fix various material issues all likely caused by QTBUG-103316 + if (matAdded && !m_puppetResetPending) { + // Workaround to fix various material issues all likely caused by QTBUG-103316 resetPuppet(); + m_puppetResetPending = true; + } refreshModel(!matAdded); int idx = m_widget->materialBrowserModel()->materialIndex(node); m_widget->materialBrowserModel()->selectMaterial(idx); @@ -261,16 +264,8 @@ void MaterialBrowserView::importsChanged(const QList &addedImports, cons m_hasQuick3DImport = hasQuick3DImport; - if (m_hasQuick3DImport) { - // Import change will trigger puppet reset. - // However, it doesn't seem to trigger the notification about the reset, so wait here. - QTimer::singleShot(1000, this, [this]() { - refreshModel(true); - }); - } else { - // No quick3d import, so we can refresh immediately to clear the browser - refreshModel(true); - } + // Import change will trigger puppet reset, so we don't want to update previews immediately + refreshModel(false); } void MaterialBrowserView::customNotification(const AbstractView *view, const QString &identifier, @@ -285,13 +280,22 @@ void MaterialBrowserView::customNotification(const AbstractView *view, const QSt int idx = m_widget->materialBrowserModel()->materialIndex(nodeList.first()); if (idx != -1) m_widget->materialBrowserModel()->selectMaterial(idx); - } else if (identifier == "reset QmlPuppet") { - // Little delay is needed to allow puppet reset to actually be done, as it is async as well - QTimer::singleShot(200, this, [this]() { - const QList materials = m_widget->materialBrowserModel()->materials(); - for (const ModelNode &node : materials) - model()->nodeInstanceView()->previewImageDataForGenericNode(node, {}); - }); + } +} + +void MaterialBrowserView::instancesCompleted(const QVector &completedNodeList) +{ + for (const ModelNode &node : completedNodeList) { + // We use root node completion as indication of puppet reset + if (node.isRootNode()) { + m_puppetResetPending = false; + QTimer::singleShot(1000, this, [this]() { + const QList materials = m_widget->materialBrowserModel()->materials(); + for (const ModelNode &node : materials) + model()->nodeInstanceView()->previewImageDataForGenericNode(node, {}); + }); + break; + } } } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h index c637efa49e1..503986a28ec 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -60,6 +60,7 @@ public: 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 instancesCompleted(const QVector &completedNodeList) override; private: void refreshModel(bool updateImages); @@ -68,6 +69,7 @@ private: QPointer m_widget; bool m_hasQuick3DImport = false; bool m_autoSelectModelMaterial = false; // TODO: wire this to some action + bool m_puppetResetPending = false; }; } // namespace QmlDesigner From 41986d794279c9a6db2e2893372707a3d5415b82 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Wed, 22 Jun 2022 12:19:57 +0300 Subject: [PATCH 34/53] QmlDesigner: Show IconButton tooltip also when button is disabled Fixes: QDS-7076 Change-Id: If412827e22c6f71a9f073d1c868dcdb325870c1d Reviewed-by: Mahmoud Badri --- .../imports/HelperWidgets/IconButton.qml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconButton.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconButton.qml index 2fc10b18554..dcd4733134a 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconButton.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconButton.qml @@ -34,10 +34,10 @@ Rectangle { signal clicked() property alias icon: icon.text - property alias enabled: mouseArea.enabled property alias tooltip: toolTip.text property alias iconSize: icon.font.pixelSize + property bool enabled: true property int buttonSize: StudioTheme.Values.height property color normalColor: StudioTheme.Values.themeControlBackground property color hoverColor: StudioTheme.Values.themeControlBackgroundHover @@ -46,9 +46,10 @@ Rectangle { width: buttonSize height: buttonSize - color: mouseArea.pressed ? pressColor - : mouseArea.containsMouse ? hoverColor - : normalColor + color: !enabled ? normalColor + : mouseArea.pressed ? pressColor + : mouseArea.containsMouse ? hoverColor + : normalColor Behavior on color { ColorAnimation { @@ -71,7 +72,11 @@ Rectangle { anchors.fill: parent hoverEnabled: true - onClicked: root.clicked() + onClicked: { + // We need to keep mouse area enabled even when button is disabled to make tooltip work + if (root.enabled) + root.clicked() + } } ToolTip { From 359f4f176755c0504a4a6372835498f8faae3fb0 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Wed, 22 Jun 2022 12:48:25 +0300 Subject: [PATCH 35/53] QmlDesigner: Fix material editor/browser texts Fixes: QDS-7083 Change-Id: Ib4a5269e275c7811a5645bbff54e3b0ec95ec302 Reviewed-by: Mats Honkamaa Reviewed-by: Mahmoud Badri --- .../materialBrowserQmlSource/MaterialBrowser.qml | 8 +++++--- .../materialEditorQmlSources/EmptyMaterialEditorPane.qml | 5 +++-- .../materialEditorQmlSources/MaterialEditorToolBar.qml | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index ab061e4289c..0419b945b9c 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -128,7 +128,7 @@ Item { StudioControls.MenuSeparator {} StudioControls.MenuItem { - text: qsTr("New Material") + text: qsTr("Create New Material") onTriggered: materialBrowserModel.addNewMaterial() } @@ -169,7 +169,8 @@ Item { } Text { - text: qsTr("No materials yet.\nClick '+' above to start.") + text: qsTr("There are no materials in this project.
Select '+' to create one.") + textFormat: Text.RichText color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.mediumFontSize horizontalAlignment: Text.AlignHCenter @@ -179,7 +180,8 @@ Item { } Text { - text: qsTr("Add QtQuick3D module using the Components view to enable the Material Browser."); + text: qsTr("To use Material Browser, first add the QtQuick3D module in the Components view."); + textFormat: Text.RichText color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.mediumFontSize topPadding: 30 diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml index 80ec524e23e..a9e272b6d51 100644 --- a/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml @@ -48,8 +48,9 @@ PropertyEditorPane { height: 150 Text { - text: hasQuick3DImport ? qsTr("No materials yet.\nClick '+' above to start.") - : qsTr("Add QtQuick3D module using the Components view to enable the Material Editor.") + text: hasQuick3DImport ? qsTr("There are no materials in this project.
Select '+' to create one.") + : qsTr("To use Material Editor, first add the QtQuick3D module in the Components view.") + textFormat: Text.RichText color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.mediumFontSize horizontalAlignment: Text.AlignHCenter diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml index c610df94c5e..80a8d9abbe9 100644 --- a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml @@ -63,7 +63,7 @@ Rectangle { buttonSize: root.height enabled: hasQuick3DImport onClicked: root.toolBarAction(ToolBarAction.AddNewMaterial) - tooltip: qsTr("Add a new material.") + tooltip: qsTr("Create new material.") } IconButton { From 2cb45cbb6b12048a1e7e02877a56d63a09e708ed Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Wed, 22 Jun 2022 14:43:43 +0300 Subject: [PATCH 36/53] QmlDesigner: Reduce number of preview requests from material editor Removed preview request call from setValue, as it is called in long loops during initial project load and added the calls outside those loops where they already weren't there. Fixes: QDS-7175 Change-Id: Ia83814a0de6fe801d954373dc8ce0e4920e8a6a6 Reviewed-by: Mahmoud Badri Reviewed-by: Thomas Hartmann --- .../components/materialeditor/materialeditorview.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index 10cd12a7c7b..49cebb3dd4b 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -556,6 +556,7 @@ void MaterialEditorView::propertiesRemoved(const QList &proper if (noValidSelection()) return; + bool changed = false; for (const AbstractProperty &property : propertyList) { ModelNode node(property.parentModelNode()); @@ -564,8 +565,11 @@ void MaterialEditorView::propertiesRemoved(const QList &proper if (node == m_selectedMaterial || QmlObjectNode(m_selectedMaterial).propertyChangeForCurrentState() == node) { setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).instanceValue(property.name())); + changed = true; } } + if (changed) + requestPreviewRender(); } void MaterialEditorView::variantPropertiesChanged(const QList &propertyList, PropertyChangeFlags /*propertyChange*/) @@ -670,6 +674,7 @@ void MaterialEditorView::instancePropertyChanged(const QList &propertyPair : propertyList) { const ModelNode modelNode = propertyPair.first; const QmlObjectNode qmlObjectNode(modelNode); @@ -681,8 +686,11 @@ void MaterialEditorView::instancePropertyChanged(const QListsetValue(qmlObjectNode, name, value); - requestPreviewRender(); m_locked = false; } From 092a1fc6a3b9668df3028b071d871f6f43f1e156 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 22 Jun 2022 14:32:02 +0200 Subject: [PATCH 37/53] QmlDesigner: Support reparenting to new nodes in transactions When we reparent to a new node that was created during the same transaction, then this node has no position. In this case we have to delete the reparented nodes and they will be created as part of the creation of the new node, since they are children. Change-Id: Icd1d02f29f529fc0f00809f7ecebf3eabfdc9a5c Reviewed-by: Miikka Heikkinen --- .../designercore/model/modeltotextmerger.cpp | 2 +- .../model/rewriteactioncompressor.cpp | 27 +++++++ .../model/rewriteactioncompressor.h | 7 +- .../qmldesigner/coretests/tst_testcore.cpp | 73 +++++++++++++++++++ .../qml/qmldesigner/coretests/tst_testcore.h | 1 + 5 files changed, 108 insertions(+), 2 deletions(-) diff --git a/src/plugins/qmldesigner/designercore/model/modeltotextmerger.cpp b/src/plugins/qmldesigner/designercore/model/modeltotextmerger.cpp index 5a4276a416f..14badab2bf6 100644 --- a/src/plugins/qmldesigner/designercore/model/modeltotextmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/modeltotextmerger.cpp @@ -222,7 +222,7 @@ void ModelToTextMerger::applyChanges() return; dumpRewriteActions(QStringLiteral("Before compression")); - RewriteActionCompressor compress(propertyOrder()); + RewriteActionCompressor compress(propertyOrder(), m_rewriterView->positionStorage()); compress(m_rewriteActions, m_rewriterView->textModifier()->tabSettings()); dumpRewriteActions(QStringLiteral("After compression")); diff --git a/src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.cpp b/src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.cpp index 24342002b38..c014bcd4d78 100644 --- a/src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.cpp +++ b/src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.cpp @@ -56,6 +56,7 @@ void RewriteActionCompressor::operator()(QList &actions, compressImports(actions); compressRereparentActions(actions); compressReparentIntoSamePropertyActions(actions); + compressReparentIntoNewPropertyActions(actions); compressPropertyActions(actions); compressAddEditRemoveNodeActions(actions); compressAddEditActions(actions, tabSettings); @@ -152,6 +153,32 @@ void RewriteActionCompressor::compressReparentIntoSamePropertyActions(QList &actions) const +{ + QList actionsToRemove; + + QList removeActions; + + for (int i = actions.size(); --i >= 0; ) { + RewriteAction *action = actions.at(i); + + if (ReparentNodeRewriteAction *reparentAction = action->asReparentNodeRewriteAction()) { + if (m_positionStore->nodeOffset(reparentAction->targetProperty().parentModelNode()) < 0) { + actionsToRemove.append(action); + + removeActions.append(new RemoveNodeRewriteAction(reparentAction->reparentedNode())); + } + } + } + + for (RewriteAction *action : qAsConst(actionsToRemove)) { + actions.removeOne(action); + delete action; + } + + actions.append(removeActions); +} + void RewriteActionCompressor::compressAddEditRemoveNodeActions(QList &actions) const { QList actionsToRemove; diff --git a/src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.h b/src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.h index 57139f98115..02c1e3ad48a 100644 --- a/src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.h +++ b/src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.h @@ -33,7 +33,10 @@ namespace Internal { class RewriteActionCompressor { public: - RewriteActionCompressor(const PropertyNameList &propertyOrder): m_propertyOrder(propertyOrder) {} + RewriteActionCompressor(const PropertyNameList &propertyOrder, ModelNodePositionStorage *positionStore) : + m_propertyOrder(propertyOrder), + m_positionStore(positionStore) + {} void operator()(QList &actions, const TextEditor::TabSettings &tabSettings) const; @@ -42,6 +45,7 @@ private: void compressRereparentActions(QList &actions) const; void compressReparentIntoSamePropertyActions(QList &actions) const; + void compressReparentIntoNewPropertyActions(QList &actions) const; void compressAddEditRemoveNodeActions(QList &actions) const; void compressPropertyActions(QList &actions) const; void compressAddEditActions(QList &actions, const TextEditor::TabSettings &tabSettings) const; @@ -49,6 +53,7 @@ private: private: PropertyNameList m_propertyOrder; + ModelNodePositionStorage *m_positionStore; }; } // namespace Internal diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp index 26b8ddcf8e3..f6708d2c8f3 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp @@ -1152,6 +1152,79 @@ void tst_TestCore::testRewriterTransactionAddingAfterReparenting() } } +void tst_TestCore::testRewriterReparentToNewNode() +{ + const QLatin1String qmlString("\n" + "import QtQuick 2.0\n" + "\n" + "Item {\n" + " Item {}\n" + " Item {}\n" + " Item {}\n" + " Item {}\n" + "}\n"); + + QPlainTextEdit textEdit; + textEdit.setPlainText(qmlString); + NotIndentingTextEditModifier modifier(&textEdit); + + QScopedPointer model(Model::create("QtQuick.Rectangle")); + + QScopedPointer testRewriterView(new TestRewriterView(0, RewriterView::Amend)); + testRewriterView->setTextModifier(&modifier); + model->attachView(testRewriterView.data()); + + QVERIFY(testRewriterView->errors().isEmpty()); + + ModelNode rootModelNode = testRewriterView->rootModelNode(); + QVERIFY(rootModelNode.isValid()); + + const QList children = rootModelNode.directSubModelNodes(); + + ModelNode rectangle = testRewriterView->createModelNode("QtQuick.Rectangle", 2, 0); + rootModelNode.nodeListProperty("data").reparentHere(rectangle); + + rectangle.setIdWithoutRefactoring("newParent"); + + QVERIFY(rectangle.isValid()); + + for (const ModelNode &child : children) + rectangle.nodeListProperty("data").reparentHere(child); + + { + RewriterTransaction transaction = testRewriterView->beginRewriterTransaction("TEST"); + ModelNode rectangle = testRewriterView->createModelNode("QtQuick.Rectangle", 2, 0); + rootModelNode.nodeListProperty("data").reparentHere(rectangle); + + rectangle.setIdWithoutRefactoring("newParent2"); + + for (const ModelNode &child : children) + rectangle.nodeListProperty("data").reparentHere(child); + } + + QCOMPARE(testRewriterView->allModelNodes().count(), 7); + + const QLatin1String expectedOutcome("\nimport QtQuick 2.0\n\n" + "Item {\n\n" + " Rectangle {\n" + " id: newParent\n" + " }\n\n" + " Rectangle {\n" + " id: newParent2\n" + " Item {\n" + " }\n\n" + " Item {\n" + " }\n\n" + " Item {\n" + " }\n\n" + " Item {\n" + " }\n" + " }\n}\n"); + + + QCOMPARE(textEdit.toPlainText(), expectedOutcome); +} + void tst_TestCore::testRewriterForGradientMagic() { const QLatin1String qmlString("\n" diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.h b/tests/auto/qml/qmldesigner/coretests/tst_testcore.h index 9a8aeb6450b..5248763fef9 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.h +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.h @@ -144,6 +144,7 @@ private slots: void testRewriterChangeImports(); void testRewriterUnicodeChars(); void testRewriterTransactionAddingAfterReparenting(); + void testRewriterReparentToNewNode(); // // unit tests QmlModelNodeFacade/QmlModelState From 9caa28754cb76e6ce5ec0e17ad85f3950e00fad2 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 22 Jun 2022 15:54:50 +0200 Subject: [PATCH 38/53] QmlDesigner: Use single transaction to move items into a layout Change-Id: Ic8eb3907d06843e7ad0d7b0117154b0b7a987808 Reviewed-by: Miikka Heikkinen --- .../components/componentcore/modelnodeoperations.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index f4f85475280..11e052b1dd3 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -475,19 +475,16 @@ static void layoutHelperFunction(const SelectionContext &selectionContext, const QmlItemNode qmlItemNode = QmlItemNode(selectionContext.firstSelectedModelNode()); if (qmlItemNode.hasInstanceParentItem()) { - ModelNode layoutNode; - selectionContext.view()->executeInTransaction("DesignerActionManager|layoutHelperFunction1",[=, &layoutNode](){ + + selectionContext.view()->executeInTransaction("DesignerActionManager|layoutHelperFunction",[=](){ QmlItemNode parentNode = qmlItemNode.instanceParentItem(); NodeMetaInfo metaInfo = selectionContext.view()->model()->metaInfo(layoutType); - layoutNode = selectionContext.view()->createModelNode(layoutType, metaInfo.majorVersion(), metaInfo.minorVersion()); + const ModelNode layoutNode = selectionContext.view()->createModelNode(layoutType, metaInfo.majorVersion(), metaInfo.minorVersion()); reparentTo(layoutNode, parentNode); - }); - - selectionContext.view()->executeInTransaction("DesignerActionManager|layoutHelperFunction2",[=](){ QList sortedSelectedNodes = selectionContext.selectedModelNodes(); Utils::sort(sortedSelectedNodes, lessThan); From cc1bf95dc2d14d4fdce32bb87938108e55c5e63c Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Wed, 22 Jun 2022 14:46:27 +0300 Subject: [PATCH 39/53] QmlDesigner: Ensure material lib node exists on project launch In this commit material library node is created on model attach. A timer keeps monitoring until it is suitable to create the node. This will properly move materials into material library for projects created with previous QDS versions. Also removed unnecessary handling on new material dragging, as rewriter now works properly with material library creation. Fixes: QDS-7178 Change-Id: Idf6f41906e02bc064961d8de9841ba1644bd3552 Reviewed-by: Thomas Hartmann --- .../materialeditor/materialeditorview.cpp | 7 ++- .../navigator/navigatortreemodel.cpp | 11 ----- .../designercore/include/abstractview.h | 1 + .../designercore/model/abstractview.cpp | 45 +++++++++++-------- 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index 49cebb3dd4b..7ab7cc090c5 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -74,8 +74,11 @@ MaterialEditorView::MaterialEditorView(QWidget *parent) connect(m_updateShortcut, &QShortcut::activated, this, &MaterialEditorView::reloadQml); m_ensureMatLibTimer.callOnTimeout([this] { - if (model() && model()->rewriterView() && !model()->rewriterView()->hasIncompleteTypeInformation()) { - materialLibraryNode(); // create the material library node + if (model() && model()->rewriterView() && !model()->rewriterView()->hasIncompleteTypeInformation() + && model()->rewriterView()->errors().isEmpty()) { + executeInTransaction("MaterialEditorView::MaterialEditorView", [this] { + ensureMaterialLibraryNode(); + }); m_ensureMatLibTimer.stop(); } }); diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index d610014d5bf..38c32f65cd8 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -697,17 +697,6 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in newQmlObjectNode.destroy(); return; } - // We can't have material initially parented if material library is created in this - // same transaction (rewriter will not allow it for some reason) - ModelNode matLib = m_view->modelNodeForId(Constants::MATERIAL_LIB_ID); - if (!matLib.isValid()) { - newQmlObjectNode.destroy(); - newQmlObjectNode = QmlItemNode::createQmlObjectNode( - m_view, itemLibraryEntry, QPointF(), NodeAbstractProperty(), false); - newModelNode = newQmlObjectNode.modelNode(); - if (!newModelNode.isValid()) - return; - } m_view->assignMaterialTo3dModel(targetNode, newModelNode); } diff --git a/src/plugins/qmldesigner/designercore/include/abstractview.h b/src/plugins/qmldesigner/designercore/include/abstractview.h index 18380aadb1a..2eecf8df5aa 100644 --- a/src/plugins/qmldesigner/designercore/include/abstractview.h +++ b/src/plugins/qmldesigner/designercore/include/abstractview.h @@ -259,6 +259,7 @@ public: void changeRootNodeType(const TypeName &type, int majorVersion, int minorVersion); + void ensureMaterialLibraryNode(); ModelNode materialLibraryNode(); void assignMaterialTo3dModel(const ModelNode &modelNode, const ModelNode &materialNode = {}); diff --git a/src/plugins/qmldesigner/designercore/model/abstractview.cpp b/src/plugins/qmldesigner/designercore/model/abstractview.cpp index 86dab970316..6826c9ed4dd 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractview.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractview.cpp @@ -809,16 +809,14 @@ void AbstractView::changeRootNodeType(const TypeName &type, int majorVersion, in m_model.data()->d->changeRootNodeType(type, majorVersion, minorVersion); } -// Returns ModelNode for project's material library. -// If the material library doesn't exist yet, it is created and all existing materials are moved -// under material library. -// This function should be called only form inside a transaction, as it potentially does many -// changes to model. -ModelNode AbstractView::materialLibraryNode() +// Creates material library if it doesn't exist and moves any existing materials into it +// This function should be called only from inside a transaction, as it potentially does many +// changes to model, or undo stack should be cleared after the call. +void AbstractView::ensureMaterialLibraryNode() { ModelNode matLib = modelNodeForId(Constants::MATERIAL_LIB_ID); if (matLib.isValid()) - return matLib; + return; // Create material library node TypeName nodeType = rootModelNode().isSubclassOf("QtQuick3D.Node") ? "QtQuick3D.Node" @@ -830,20 +828,28 @@ ModelNode AbstractView::materialLibraryNode() rootModelNode().defaultNodeListProperty().reparentHere(matLib); const QList materials = rootModelNode().subModelNodesOfType("QtQuick3D.Material"); - if (materials.isEmpty()) - return matLib; + if (!materials.isEmpty()) { + // Move all materials to under material library node + for (const ModelNode &node : materials) { + // If material has no name, set name to id + QString matName = node.variantProperty("objectName").value().toString(); + if (matName.isEmpty()) { + VariantProperty objNameProp = node.variantProperty("objectName"); + objNameProp.setValue(node.id()); + } - // Move all materials to under material library node - for (const ModelNode &node : materials) { - // If material has no name, set name to id - QString matName = node.variantProperty("objectName").value().toString(); - if (matName.isEmpty()) { - VariantProperty objNameProp = node.variantProperty("objectName"); - objNameProp.setValue(node.id()); + matLib.defaultNodeListProperty().reparentHere(node); } - - matLib.defaultNodeListProperty().reparentHere(node); } +} + +// Returns ModelNode for project's material library. +ModelNode AbstractView::materialLibraryNode() +{ + ensureMaterialLibraryNode(); + + ModelNode matLib = modelNodeForId(Constants::MATERIAL_LIB_ID); + QTC_ASSERT(matLib.isValid(), return {}); return matLib; } @@ -859,6 +865,9 @@ void AbstractView::assignMaterialTo3dModel(const ModelNode &modelNode, const Mod QTC_ASSERT(modelNode.isValid() && modelNode.isSubclassOf("QtQuick3D.Model"), return); ModelNode matLib = materialLibraryNode(); + + QTC_ASSERT(matLib.isValid(), return); + ModelNode newMaterialNode; if (materialNode.isValid() && materialNode.isSubclassOf("QtQuick3D.Material")) { From b63675db6ece28e1a7f896f297724f4ebaf47f2e Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 22 Jun 2022 17:10:58 +0200 Subject: [PATCH 40/53] QmlDesigner: Show metainfo when node is created in debugview Change-Id: Id377b3e29f0a953a206dc3aa08008bb87f12bf0a Reviewed-by: Thomas Hartmann --- src/plugins/qmldesigner/components/debugview/debugview.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/components/debugview/debugview.cpp b/src/plugins/qmldesigner/components/debugview/debugview.cpp index be09b3864a4..831170d3026 100644 --- a/src/plugins/qmldesigner/components/debugview/debugview.cpp +++ b/src/plugins/qmldesigner/components/debugview/debugview.cpp @@ -110,9 +110,11 @@ void DebugView::nodeCreated(const ModelNode &createdNode) QString string; message.setString(&string); message << createdNode; + message << createdNode.majorVersion() << "." << createdNode.minorVersion(); message << createdNode.nodeSource(); - message << "MetaInfo " << createdNode.metaInfo().isValid(); + message << "MetaInfo " << createdNode.metaInfo().isValid() << " "; if (createdNode.metaInfo().isValid()) { + message << createdNode.metaInfo().majorVersion() << "." << createdNode.metaInfo().minorVersion(); message << createdNode.metaInfo().componentFileName(); } log("::nodeCreated:", message.readAll()); From 5fa46d665971bc0c7fbd2373400c5f2120ece922 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 22 Jun 2022 17:11:51 +0200 Subject: [PATCH 41/53] QmlDesigner: Add createModelNode without explicit versions This makes it easier to create properly versioned ModelNodes. Change-Id: I2a81021daa5e57af6740e447826124f1554a2c78 Reviewed-by: Thomas Hartmann --- .../qmldesigner/designercore/include/abstractview.h | 2 ++ .../qmldesigner/designercore/model/abstractview.cpp | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/plugins/qmldesigner/designercore/include/abstractview.h b/src/plugins/qmldesigner/designercore/include/abstractview.h index 2eecf8df5aa..6971f5f39ed 100644 --- a/src/plugins/qmldesigner/designercore/include/abstractview.h +++ b/src/plugins/qmldesigner/designercore/include/abstractview.h @@ -134,6 +134,8 @@ public: RewriterTransaction beginRewriterTransaction(const QByteArray &identifier); + ModelNode createModelNode(const TypeName &typeName); + ModelNode createModelNode(const TypeName &typeName, int majorVersion, int minorVersion, diff --git a/src/plugins/qmldesigner/designercore/model/abstractview.cpp b/src/plugins/qmldesigner/designercore/model/abstractview.cpp index 6826c9ed4dd..e728c61358f 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractview.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractview.cpp @@ -31,6 +31,7 @@ #include "nodeinstanceview.h" #include #include +#include #include #include #include @@ -89,6 +90,12 @@ RewriterTransaction AbstractView::beginRewriterTransaction(const QByteArray &ide return RewriterTransaction(this, identifier); } +ModelNode AbstractView::createModelNode(const TypeName &typeName) +{ + const NodeMetaInfo metaInfo = model()->metaInfo(typeName); + return createModelNode(typeName, metaInfo.majorVersion(), metaInfo.minorVersion()); +} + ModelNode AbstractView::createModelNode(const TypeName &typeName, int majorVersion, int minorVersion, From a0d474f2defd3ac14ae118d5a7e3cc99b5aab9f6 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 22 Jun 2022 17:13:25 +0200 Subject: [PATCH 42/53] QmlDesigner: Create transitions with proper versions The versions were hard coded which leads to issues. Task-number: QDS-6760 Change-Id: I94c3599348b996bb700da636cd63e74ea4c02be6 Reviewed-by: Reviewed-by: Thomas Hartmann --- .../transitioneditor/transitioneditorview.cpp | 84 ++++++++++--------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp index f487cc18fb7..14c5eb6e953 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp @@ -251,50 +251,54 @@ ModelNode TransitionEditorView::addNewTransition() if (!idPropertyList.isEmpty()) { executeInTransaction( - " TransitionEditorView::addNewTransition", [&transition, idPropertyList, root, this]() { - transition = createModelNode("QtQuick.Transition", - 2, - 0, - {{ - "from", - "*", - }, - { - "to", - "*", - }}); - transition.setAuxiliaryData("transitionDuration", 2000); - transition.validId(); - root.nodeListProperty("transitions").reparentHere(transition); + " TransitionEditorView::addNewTransition", [&transition, idPropertyList, root, this]() { - for (auto it = idPropertyList.cbegin(); it != idPropertyList.cend(); ++it) { - ModelNode parallelAnimation = createModelNode("QtQuick.ParallelAnimation", - 2, - 12); - transition.defaultNodeAbstractProperty().reparentHere(parallelAnimation); - for (const QString &property : it.value()) { - ModelNode sequentialAnimation - = createModelNode("QtQuick.SequentialAnimation", 2, 12); - parallelAnimation.defaultNodeAbstractProperty().reparentHere( - sequentialAnimation); + const NodeMetaInfo transitionMetaInfo = model()->metaInfo("QtQuick.Transition"); + transition = createModelNode("QtQuick.Transition", + transitionMetaInfo.majorVersion(), + transitionMetaInfo.minorVersion(), + {{ + "from", + "*", + }, + { + "to", + "*", + }}); + transition.setAuxiliaryData("transitionDuration", 2000); + transition.validId(); + root.nodeListProperty("transitions").reparentHere(transition); - ModelNode pauseAnimation = createModelNode("QtQuick.PauseAnimation", - 2, - 12, - {{"duration", 50}}); - sequentialAnimation.defaultNodeAbstractProperty().reparentHere( - pauseAnimation); + for (auto it = idPropertyList.cbegin(); it != idPropertyList.cend(); ++it) { + ModelNode parallelAnimation = createModelNode("QtQuick.ParallelAnimation"); + transition.defaultNodeAbstractProperty().reparentHere(parallelAnimation); + for (const QString &property : it.value()) { + ModelNode sequentialAnimation + = createModelNode("QtQuick.SequentialAnimation"); + parallelAnimation.defaultNodeAbstractProperty().reparentHere( + sequentialAnimation); - ModelNode propertyAnimation = createModelNode("QtQuick.PropertyAnimation", - 2, - 12, - {{"property", property}, - {"duration", 150}}); - propertyAnimation.bindingProperty("target").setExpression(it.key()); - sequentialAnimation.defaultNodeAbstractProperty().reparentHere( - propertyAnimation); - } + const NodeMetaInfo pauseMetaInfo = model()->metaInfo("QtQuick.PauseAnimation"); + + ModelNode pauseAnimation = createModelNode("QtQuick.PauseAnimation", + pauseMetaInfo.majorVersion(), + pauseMetaInfo.minorVersion(), + {{"duration", 50}}); + sequentialAnimation.defaultNodeAbstractProperty().reparentHere( + pauseAnimation); + + const NodeMetaInfo propertyMetaInfo = model()->metaInfo("QtQuick.PauseAnimation"); + + ModelNode propertyAnimation = createModelNode("QtQuick.PropertyAnimation", + propertyMetaInfo.majorVersion(), + propertyMetaInfo.minorVersion(), + {{"property", property}, + {"duration", 150}}); + propertyAnimation.bindingProperty("target").setExpression(it.key()); + sequentialAnimation.defaultNodeAbstractProperty().reparentHere( + propertyAnimation); } + } }); } else { QString properties; From 5903d82c0b6356d1bb3460ed2b934ba2e87774ce Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Thu, 16 Jun 2022 12:29:50 +0200 Subject: [PATCH 43/53] QmlDesigner: Fix hover color for ButtonRow Use the default background color for ButtonRow buttons on global hover. This needs to be done to distinguish between hover and global hover on ButtonRow buttons as we unified the two colors as an attempt to make the UI a bit less noisy. Change-Id: I63f9c730367e9b906a772a63210c7ca548647352 Reviewed-by: Brook Cronin Reviewed-by: Reviewed-by: Thomas Hartmann --- .../imports/StudioControls/AbstractButton.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/AbstractButton.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/AbstractButton.qml index 577257de709..c594220d5e0 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/AbstractButton.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/AbstractButton.qml @@ -136,7 +136,7 @@ T.AbstractButton { when: myButton.globalHover && !myButton.hover && !myButton.pressed && myButton.enabled PropertyChanges { target: buttonBackground - color: StudioTheme.Values.themeControlBackgroundGlobalHover + color: StudioTheme.Values.themeControlBackground } }, State { From b3be9d9802850718c10b08602cf41dd109040c8f Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 23 Jun 2022 11:59:20 +0300 Subject: [PATCH 44/53] QmlDesigner: Tweak 3D asset import dialog layout On some font scalings, spinboxes were too tight, especially when there was just one row of options showing, so increased the row height and tweaked layout margins a bit. Fixes: QDS-7186 Change-Id: I0650ab976d96f27fc9cf606c4faa6fa2d7c5e8f4 Reviewed-by: Thomas Hartmann --- .../components/itemlibrary/itemlibraryassetimportdialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp index 0fe286a9982..dc29dab8c1b 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp @@ -74,7 +74,7 @@ static void addFormattedMessage(Utils::OutputFormatter *formatter, const QString formatter->plainTextEdit()->verticalScrollBar()->maximum()); } -static const int rowHeight = 28; +static const int rowHeight = 32; static const int checkBoxColWidth = 18; static const int labelMinWidth = 130; static const int controlMinWidth = 65; @@ -781,7 +781,7 @@ QGridLayout *ItemLibraryAssetImportDialog::createOptionsGrid( int &globalOptionsHeight = advanced ? m_advancedData.optionsHeight : m_simpleData.optionsHeight; globalOptionRows = qMax(globalOptionRows, optionRows); globalOptionsHeight = qMax(rowHeight * optionRows + 20, globalOptionsHeight); - layout->setContentsMargins(8, 8, 8, 0); + layout->setContentsMargins(8, 6, 8, 0); return layout; } From 7aaaccb2812d2eaa237861f914ef3fae3ba1dcee Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 23 Jun 2022 14:13:29 +0300 Subject: [PATCH 45/53] QmlDesigner: Remove duplicate assert This assert was redundant, leading to duplicate warnings if triggered. Change-Id: I59b6a7c17345a2ccba0d51ef50a303eec2026422 Reviewed-by: Mahmoud Badri --- src/plugins/qmldesigner/designercore/model/abstractview.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/designercore/model/abstractview.cpp b/src/plugins/qmldesigner/designercore/model/abstractview.cpp index e728c61358f..046572d7140 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractview.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractview.cpp @@ -873,7 +873,8 @@ void AbstractView::assignMaterialTo3dModel(const ModelNode &modelNode, const Mod ModelNode matLib = materialLibraryNode(); - QTC_ASSERT(matLib.isValid(), return); + if (!matLib.isValid()) + return; ModelNode newMaterialNode; From 8a8a2f5c5558d02eb1efb5c5e33d18c7d6b7e35d Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 23 Jun 2022 14:20:56 +0300 Subject: [PATCH 46/53] QmlDesigner: Unify the background of 3D preview images Added 3D checkerboard floor on all 3D previews. Since shadows generally look bad for arbitrary 3D scenes, and would mostly be obscured by the model itself as we want the light mostly from the front, removed shadows also from the material preview to unify the previews. Qt5 had issues rendering the 3D floor texture, so used a static image there, as Qt5 only generates previews of one size. Fixes: QDS-7078 Change-Id: I74d094878ed01e6e531ad60df1f8d9d7cf415860 Reviewed-by: Reviewed-by: Mahmoud Badri --- .../qtcreator/qml/qmlpuppet/editor3d_qt5.qrc | 2 +- .../mockfiles/images/static_floor.png | Bin 0 -> 2685 bytes .../mockfiles/qt5/MaterialNodeView.qml | 24 ---------- .../mockfiles/qt5/ModelNode3DImageView.qml | 9 ++-- .../qmlpuppet/mockfiles/qt5/ModelNodeView.qml | 2 +- .../mockfiles/qt6/MaterialNodeView.qml | 23 --------- .../mockfiles/qt6/ModelNode3DImageView.qml | 44 ++++++++++++++++++ .../qmlpuppet/mockfiles/qt6/ModelNodeView.qml | 2 +- 8 files changed, 51 insertions(+), 55 deletions(-) create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/images/static_floor.png diff --git a/share/qtcreator/qml/qmlpuppet/editor3d_qt5.qrc b/share/qtcreator/qml/qmlpuppet/editor3d_qt5.qrc index 7e023c127b7..bbe9a910db6 100644 --- a/share/qtcreator/qml/qmlpuppet/editor3d_qt5.qrc +++ b/share/qtcreator/qml/qmlpuppet/editor3d_qt5.qrc @@ -13,7 +13,7 @@ mockfiles/images/directional@2x.png mockfiles/images/point.png mockfiles/images/point@2x.png - mockfiles/images/floor_tex.png + mockfiles/images/static_floor.png mockfiles/images/spot.png mockfiles/images/spot@2x.png mockfiles/qt5/AdjustableArrow.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/images/static_floor.png b/share/qtcreator/qml/qmlpuppet/mockfiles/images/static_floor.png new file mode 100644 index 0000000000000000000000000000000000000000..93073719f55f658c6dfcfb2a2f45e4839567a12b GIT binary patch literal 2685 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 Iol`;+0Psp60ssI2 literal 0 HcmV?d00001 diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/MaterialNodeView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/MaterialNodeView.qml index c36d8c227c3..9fee06e0ad1 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/MaterialNodeView.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/MaterialNodeView.qml @@ -45,10 +45,6 @@ View3D { Node { DirectionalLight { - shadowMapQuality: Light.ShadowMapQualityMedium - shadowFilter: 20 - shadowFactor: 21 - castsShadow: true eulerRotation.x: -26 eulerRotation.y: -57 } @@ -68,25 +64,5 @@ View3D { source: "#Sphere" materials: previewMaterial } - - Model { - id: floorModel - source: "#Rectangle" - scale.y: 8 - scale.x: 8 - eulerRotation.x: -90 - materials: floorMaterial - DefaultMaterial { - id: floorMaterial - diffuseMap: floorTex - - Texture { - id: floorTex - source: "../images/floor_tex.png" - scaleU: floorModel.scale.x - scaleV: floorModel.scale.y - } - } - } } } diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNode3DImageView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNode3DImageView.qml index a6d5c6b1db5..70b9dbc4d0e 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNode3DImageView.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNode3DImageView.qml @@ -123,14 +123,13 @@ Item { anchors.fill: parent } - Rectangle { + // We can use static image in Qt5 as only small previews will be generated + Image { id: backgroundRect anchors.fill: parent z: -1 - gradient: Gradient { - GradientStop { position: 1.0; color: "#222222" } - GradientStop { position: 0.0; color: "#999999" } - } + source: "../images/static_floor.png" + fillMode: Image.Stretch } } } diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNodeView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNodeView.qml index b497b0419a1..82688bbef5a 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNodeView.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNodeView.qml @@ -70,7 +70,7 @@ View3D { materials: [ DefaultMaterial { - diffuseColor: "#4aee45" + diffuseColor: "#999999" } ] } diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/MaterialNodeView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/MaterialNodeView.qml index ed5cfea56f4..94051d5f6e6 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/MaterialNodeView.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/MaterialNodeView.qml @@ -45,10 +45,6 @@ View3D { Node { DirectionalLight { - shadowMapQuality: Light.ShadowMapQualityMedium - shadowFilter: 20 - shadowFactor: 21 - castsShadow: true eulerRotation.x: -26 eulerRotation.y: -57 } @@ -70,24 +66,5 @@ View3D { materials: previewMaterial } - Model { - id: floorModel - source: "#Rectangle" - scale.y: 8 - scale.x: 8 - eulerRotation.x: -90 - materials: floorMaterial - DefaultMaterial { - id: floorMaterial - diffuseMap: floorTex - - Texture { - id: floorTex - source: "../images/floor_tex.png" - scaleU: floorModel.scale.x - scaleV: floorModel.scale.y - } - } - } } } diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNode3DImageView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNode3DImageView.qml index 5caac0047c4..031d01d65fb 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNode3DImageView.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNode3DImageView.qml @@ -126,6 +126,50 @@ Item { GradientStop { position: 1.0; color: "#222222" } GradientStop { position: 0.0; color: "#999999" } } + + // Use View3D instead of static image to make background look good on all resolutions + View3D { + anchors.fill: parent + environment: sceneEnv + + SceneEnvironment { + id: sceneEnv + antialiasingMode: SceneEnvironment.MSAA + antialiasingQuality: SceneEnvironment.High + } + + DirectionalLight { + eulerRotation.x: -26 + eulerRotation.y: -57 + } + + PerspectiveCamera { + y: 125 + z: 120 + eulerRotation.x: -31 + clipNear: 1 + clipFar: 1000 + } + + Model { + id: floorModel + source: "#Rectangle" + scale.y: 8 + scale.x: 8 + eulerRotation.x: -90 + materials: floorMaterial + DefaultMaterial { + id: floorMaterial + diffuseMap: floorTex + Texture { + id: floorTex + source: "../images/floor_tex.png" + scaleU: floorModel.scale.x + scaleV: floorModel.scale.y + } + } + } + } } } } diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNodeView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNodeView.qml index 4da76ea097f..ea2e23837fd 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNodeView.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNodeView.qml @@ -70,7 +70,7 @@ View3D { materials: [ DefaultMaterial { - diffuseColor: "#4aee45" + diffuseColor: "#999999" } ] } From 93cd068d3e6cadb78f007975853828be35a4e1c6 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 23 Jun 2022 15:37:26 +0300 Subject: [PATCH 47/53] QmlDesigner: Create material library at import change There are still rewriter issues with Qt5, where component library templates for models contain materials, so create material library when quick3d import is added to ensure we never need to create it when models are added. Change-Id: I7006a39228d316dbfd84f49d19c025bb42b6765c Reviewed-by: Mahmoud Badri --- .../components/materialeditor/materialeditorview.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index 7ab7cc090c5..8b3d6932741 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -718,6 +718,9 @@ void MaterialEditorView::importsChanged(const QList &addedImports, const m_hasQuick3DImport = model()->hasImport("QtQuick3D"); m_qmlBackEnd->contextObject()->setHasQuick3DImport(m_hasQuick3DImport); + if (m_hasQuick3DImport) + m_ensureMatLibTimer.start(500); + resetView(); } From 34491bdfc34e518b902c1dd2085ef64d8680e315 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Thu, 23 Jun 2022 16:20:39 +0200 Subject: [PATCH 48/53] QmlDesigner: Only remove node with position Extending the test case. Change-Id: I37255de763262e069c2f9d30b1ce584a0347fbcf Reviewed-by: Reviewed-by: Thomas Hartmann --- .../model/rewriteactioncompressor.cpp | 5 +++- .../qmldesigner/coretests/tst_testcore.cpp | 27 +++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.cpp b/src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.cpp index c014bcd4d78..0662da4e86a 100644 --- a/src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.cpp +++ b/src/plugins/qmldesigner/designercore/model/rewriteactioncompressor.cpp @@ -166,7 +166,10 @@ void RewriteActionCompressor::compressReparentIntoNewPropertyActions(QListnodeOffset(reparentAction->targetProperty().parentModelNode()) < 0) { actionsToRemove.append(action); - removeActions.append(new RemoveNodeRewriteAction(reparentAction->reparentedNode())); + const ModelNode childNode = reparentAction->reparentedNode(); + + if (m_positionStore->nodeOffset(childNode) > 0) + removeActions.append(new RemoveNodeRewriteAction(childNode)); } } } diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp index f6708d2c8f3..b6ff4903c43 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp @@ -1181,7 +1181,7 @@ void tst_TestCore::testRewriterReparentToNewNode() const QList children = rootModelNode.directSubModelNodes(); - ModelNode rectangle = testRewriterView->createModelNode("QtQuick.Rectangle", 2, 0); + ModelNode rectangle = testRewriterView->createModelNode("QtQuick.Rectangle"); rootModelNode.nodeListProperty("data").reparentHere(rectangle); rectangle.setIdWithoutRefactoring("newParent"); @@ -1193,7 +1193,7 @@ void tst_TestCore::testRewriterReparentToNewNode() { RewriterTransaction transaction = testRewriterView->beginRewriterTransaction("TEST"); - ModelNode rectangle = testRewriterView->createModelNode("QtQuick.Rectangle", 2, 0); + ModelNode rectangle = testRewriterView->createModelNode("QtQuick.Rectangle"); rootModelNode.nodeListProperty("data").reparentHere(rectangle); rectangle.setIdWithoutRefactoring("newParent2"); @@ -1223,6 +1223,29 @@ void tst_TestCore::testRewriterReparentToNewNode() QCOMPARE(textEdit.toPlainText(), expectedOutcome); + + rectangle.destroy(); + + QCOMPARE(testRewriterView->allModelNodes().count(), 6); + + { + RewriterTransaction transaction = testRewriterView->beginRewriterTransaction("TEST"); + + ModelNode newChild = testRewriterView->createModelNode("QtQuick.Rectangle"); + rootModelNode.nodeListProperty("data").reparentHere(newChild); + newChild.setIdWithoutRefactoring("newChild"); + ModelNode newParent = testRewriterView->createModelNode("QtQuick.Rectangle"); + rootModelNode.nodeListProperty("data").reparentHere(newParent); + + newParent.setIdWithoutRefactoring("newParent3"); + + for (const ModelNode &child : children) + newParent.nodeListProperty("data").reparentHere(child); + + newParent.nodeListProperty("data").reparentHere(newChild); + } + + QCOMPARE(testRewriterView->allModelNodes().count(), 8); } void tst_TestCore::testRewriterForGradientMagic() From 21ef02a016f7921fab8f72e3fcea2147bb2cd018 Mon Sep 17 00:00:00 2001 From: Brook Cronin Date: Fri, 24 Jun 2022 11:23:06 +0200 Subject: [PATCH 49/53] update fixed version of icon font Change-Id: Iedf945b3718d4df245e24ac1ba756b20f6ed9cf1 Reviewed-by: Thomas Hartmann --- .../imports/StudioTheme/icons.ttf | Bin 23272 -> 23512 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf index 5a3e99d4590b33cddc9812e0435c01e03ca491c6..cefd1d7d86696ac13a11199df9b1203d65947b94 100644 GIT binary patch delta 5139 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 From a627d43d39bc17e7144fda724cb41d944bded6a5 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Mon, 27 Jun 2022 11:07:32 +0300 Subject: [PATCH 50/53] QmlDesigner: Fix crash on mode change Added model pointer validity checks into a few places. Fixes: QDS-7191 Change-Id: I94beb134f4d9a0b1c7fc9dc87da1d02ad255beaa Reviewed-by: Thomas Hartmann --- .../components/materialbrowser/materialbrowserview.cpp | 2 ++ .../components/materialeditor/materialeditorview.cpp | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 2a06d5acbc4..2d02ba12768 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -290,6 +290,8 @@ void MaterialBrowserView::instancesCompleted(const QVector &completed if (node.isRootNode()) { m_puppetResetPending = false; QTimer::singleShot(1000, this, [this]() { + if (!model() || !model()->nodeInstanceView()) + return; const QList materials = m_widget->materialBrowserModel()->materials(); for (const ModelNode &node : materials) model()->nodeInstanceView()->previewImageDataForGenericNode(node, {}); diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index 8b3d6932741..d86ef1463bf 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -416,6 +416,8 @@ void MaterialEditorView::handleToolBarAction(int action) } case MaterialEditorContextObject::AddNewMaterial: { + if (!model()) + break; executeInTransaction("MaterialEditorView:handleToolBarAction", [&] { NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.DefaultMaterial"); ModelNode newMatNode = createModelNode("QtQuick3D.DefaultMaterial", metaInfo.majorVersion(), @@ -634,7 +636,7 @@ void MaterialEditorView::auxiliaryDataChanged(const ModelNode &node, const Prope // request render image for the selected material node void MaterialEditorView::requestPreviewRender() { - if (m_selectedMaterial.isValid()) + if (model() && model()->nodeInstanceView() && m_selectedMaterial.isValid()) model()->nodeInstanceView()->previewImageDataForGenericNode(m_selectedMaterial, {}); } @@ -740,6 +742,9 @@ void MaterialEditorView::duplicateMaterial(const ModelNode &material) { QTC_ASSERT(material.isValid(), return); + if (!model()) + return; + TypeName matType = material.type(); QmlObjectNode sourceMat(material); From 73af75fe68eb0af6ef396a8ef90693c8fdd0f8e6 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 28 Jun 2022 12:38:12 +0300 Subject: [PATCH 51/53] QmlDesigner: Fix crash in material browser Task-number: QDS-7191 Change-Id: I9542a227aeb3f462ff3572e9a07a7473f98bfa61 Reviewed-by: Thomas Hartmann --- .../components/materialbrowser/materialbrowserview.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 2d02ba12768..d85e8239c12 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -107,6 +107,9 @@ void MaterialBrowserView::modelAttached(Model *model) void MaterialBrowserView::refreshModel(bool updateImages) { + if (!model() || !model()->nodeInstanceView()) + return; + ModelNode matLib = modelNodeForId(Constants::MATERIAL_LIB_ID); QList materials; From ee40ed19e55a47dd109d1b7f4faf9a44ed97fc0f Mon Sep 17 00:00:00 2001 From: Mats Honkamaa Date: Wed, 22 Jun 2022 12:26:03 +0300 Subject: [PATCH 52/53] Doc: Update timeline documentation - Added a task topic for binding a timeline to a property. - Added topic on adding multilpe timelines. - Some changes to focus more on the task rather than function. - Other minor updates. Task-number: QDS-6879 Change-Id: Ibd99785d6a9ee70fc10b8e1395ef8aaf0e186ddc Reviewed-by: Mahmoud Badri Reviewed-by: Leena Miettinen --- .../images/timeline-bind-animation-state.png | Bin 0 -> 4262 bytes .../images/timeline-insert-keyframe.png | Bin 0 -> 8974 bytes .../timeline-per-property-recording.png | Bin 0 -> 6041 bytes .../timeline-settings-dialog-second.png | Bin 0 -> 16649 bytes .../images/timeline-settings-dialog.png | Bin 0 -> 14019 bytes .../timeline-settings-property-binding.png | Bin 0 -> 14993 bytes doc/qtdesignstudio/images/timeline-states.png | Bin 0 -> 12233 bytes .../qtquick-component-context-menu.qdocinc | 2 +- .../src/views/qtquick-timeline-view.qdoc | 14 +- .../src/views/qtquick-timeline.qdoc | 244 +++++++++++------- 10 files changed, 164 insertions(+), 96 deletions(-) create mode 100644 doc/qtdesignstudio/images/timeline-bind-animation-state.png create mode 100644 doc/qtdesignstudio/images/timeline-insert-keyframe.png create mode 100644 doc/qtdesignstudio/images/timeline-per-property-recording.png create mode 100644 doc/qtdesignstudio/images/timeline-settings-dialog-second.png create mode 100644 doc/qtdesignstudio/images/timeline-settings-dialog.png create mode 100644 doc/qtdesignstudio/images/timeline-settings-property-binding.png create mode 100644 doc/qtdesignstudio/images/timeline-states.png diff --git a/doc/qtdesignstudio/images/timeline-bind-animation-state.png b/doc/qtdesignstudio/images/timeline-bind-animation-state.png new file mode 100644 index 0000000000000000000000000000000000000000..7e65c85a30c231a8777fe2f7f39bb2b62eb51a89 GIT binary patch literal 4262 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 literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/images/timeline-insert-keyframe.png b/doc/qtdesignstudio/images/timeline-insert-keyframe.png new file mode 100644 index 0000000000000000000000000000000000000000..c46a711806729498ecffc5883f41c5b06b39d404 GIT binary patch literal 8974 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 O7(8A5T-G@yGywoEr=n&6 literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/images/timeline-settings-dialog-second.png b/doc/qtdesignstudio/images/timeline-settings-dialog-second.png new file mode 100644 index 0000000000000000000000000000000000000000..03cd0be3555d9ea6d40dfbf2d5dfc03bf8390d38 GIT binary patch literal 16649 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
k{yb?d)w3gvnA^W~91#{C~peo30W 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 literal 0 HcmV?d00001 diff --git a/doc/qtdesignstudio/images/timeline-states.png b/doc/qtdesignstudio/images/timeline-states.png new file mode 100644 index 0000000000000000000000000000000000000000..a1dc73e51eba60394b6aeff7228a0bbcbfb5f642 GIT binary patch literal 12233 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 \uicontrol {Insert Keyframe} for the property that you want to animate. + \image timeline-insert-keyframe.png \li In the \l Timeline view, select the \uicontrol {Per Property Recording} button to start recording property changes. - \li Check that the playhead is in frame 0 and enter the value of the + \image timeline-per-property-recording.png + \li Ensure that the playhead is in frame 0 and enter the value of the property in the field next to the property name on the timeline. Press \key Enter to save the value. \li Move the playhead to another frame on the timeline and specify @@ -148,11 +151,67 @@ \uicontrol {Per Property Recording} again to stop recording. \endlist + \section2 Binding a Timeline to a Property + + When you bind a timeline to a component property, the animation's + current frame is controlled by the value of the property. + + In this example, you bind the timeline to a slider component. + + With a timeline created and keyframe values set: + + \list 1 + \li From \uicontrol {Components}, drag a slider to + \uicontrol {Form Editor} or \uicontrol {Navigator}. + \li In \uicontrol {Navigator}, select \e slider and in + \uicontrol {Properties}, set: + \list + \li \uicontrol To to 1000. + \note The \uicontrol From and \uicontrol To values of the slider + should match the \uicontrol {Start Frame} and + \uicontrol {End Frame} values of the timeline if you want to + control the complete animation with the slider. + \endlist + \li In the \uicontrol {Timeline Settings} dialog, select + \inlineimage icons/minus.png + next to the \uicontrol {Animation Settings} tab to delete the + animation. If you have several animations, delete all. + \li In \uicontrol {Expression binding}, enter \c {slider.value}. + \image timeline-settings-property-binding.png + \endlist + + \section2 Binding Animations to States + + You can bind animations to states, this means that the animation will run + when you enter the state. + + To bind an animation to a state: + \list 1 + \li In the table at the bottom of the \uicontrol {Timeline Settings} + dialog lists: + \list + \li Double-click the value in the \uicontrol Timeline field and select + the timeline with the animation you want to bind to the state. + \li Double-click the value in the \uicontrol Animation field and + select the animation you want to bind to the state. + \image timeline-bind-animation-state.png + \endlist + \endlist + To bind a state to a certain keyframe in an animation without running the + animation, set the keyframe in the \uicontrol{Fixed Frame} field. + + \section1 Managing Keyframes + + \image studio-timeline-with-tracks.png "Timeline view" + + \section2 Editing Keyframes + To remove all the changes you recorded for a property, right-click the property name on the timeline and select \uicontrol {Remove Property}. To add keyframes to the keyframe track of a component at the current - position of the playhead, select \uicontrol {Add Keyframes at Current Frame}. + position of the playhead, right-click the component name on the timeline and + select \uicontrol {Add Keyframes at Current Frame}. Keyframes are marked on the timeline by using \l{keyframe_marker}{markers} of different colors and shapes, depending on whether they are active or @@ -162,7 +221,7 @@ \section2 Editing Keyframe Values To fine-tune the value of a keyframe, double-click a keyframe marker or - select \uicontrol {Edit Keyframe} in the context menu. + right-click it and select \uicontrol {Edit Keyframe} in the context menu. The \uicontrol {Edit Keyframe} dialog displays the name of the property you are animating and its current value at the frame specified in the @@ -173,27 +232,36 @@ \section2 Copying Keyframes You can copy the keyframes from the keyframe track for a component and - paste them to the keyframe track of another component. To copy all - keyframes from one track to another one, first right-click the component ID - and select \uicontrol {Copy All Keyframes} in the context menu. - Then right-click the other component ID, and select - \uicontrol {Paste Keyframes} in the context menu. + paste them to the keyframe track of another component. + + To copy all keyframes from one track to another one: + \list 1 + \li Right-click the component ID and select + \uicontrol {Copy All Keyframes} in the context menu. + \li Right-click the other component ID, and select + \uicontrol {Paste Keyframes} in the context menu. + \endlist \section2 Deleting Keyframes - To delete the selected keyframe, select \uicontrol {Delete Keyframe} in the - context menu. + To delete a keyframe, right-click it and select \uicontrol {Delete Keyframe} + in the context menu. - To delete all keyframes from the selected component, select + To delete all keyframes from the selected component, right-click the + component name in \uicontrol {Timeline} and select \uicontrol {Delete All Keyframes} in the context menu. \section1 Viewing the Animation - You can view the animation on the canvas by moving the playhead along the - timeline. + To preview your animation, do one of the following in the + \uicontrol{Timeline} view: + \list + \li Drag the playhead along the timeline. + \li Select \inlineimage icons/start_playback.png + button or press \key Space. + \endlist - To preview the animation, select the \uicontrol {Play (Space)} - button or press \key Space. To preview the whole UI, select the + To preview the whole UI, select the \inlineimage icons/live_preview.png (\uicontrol {Show Live Preview}) button on the canvas toolbar or press \key {Alt+P}. From 234958a47a6edcd17bae255411a5b0f0bbaea6c7 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Mon, 27 Jun 2022 14:31:59 +0300 Subject: [PATCH 53/53] QmlDesigner: Enable drag-n-drop a material to a model in Navigator Fixes: QDS-6694 Change-Id: I2fb32052559b1d459cc8025e9f30368b0189e8ab Reviewed-by: Miikka Heikkinen Reviewed-by: Reviewed-by: Thomas Hartmann --- .../materialBrowserQmlSource/MaterialItem.qml | 7 +++-- .../materialbrowser/materialbrowserwidget.cpp | 28 +++++++++++++++++ .../materialbrowser/materialbrowserwidget.h | 4 +++ .../choosefrompropertylistdialog.cpp | 5 ++++ .../navigator/navigatortreemodel.cpp | 30 +++++++++++++++++++ .../components/navigator/navigatortreemodel.h | 1 + .../components/navigator/navigatorview.cpp | 9 ++++++ .../qmldesigner/qmldesignerconstants.h | 1 + 8 files changed, 83 insertions(+), 2 deletions(-) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml index 4eeb120b27c..640383d4e18 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml @@ -72,9 +72,12 @@ Rectangle { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: (mouse) => { + onPressed: (mouse) => { materialBrowserModel.selectMaterial(index) - if (mouse.button === Qt.RightButton) + + if (mouse.button === Qt.LeftButton) + rootView.startDragMaterial(index, mapToGlobal(mouse.x, mouse.y)) + else if (mouse.button === Qt.RightButton) root.showContextMenu() } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index 156add5d2da..3982d534546 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -103,6 +104,27 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) if (event->type() == QEvent::FocusOut) { if (obj == m_quickWidget.data()) QMetaObject::invokeMethod(m_quickWidget->rootObject(), "closeContextMenu"); + } else if (event->type() == QMouseEvent::MouseMove) { + DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); + QTC_ASSERT(document, return false); + Model *model = document->currentModel(); + QTC_ASSERT(model, return false); + + if (m_materialToDrag.isValid()) { + QMouseEvent *me = static_cast(event); + if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 10) { + QByteArray data; + QMimeData *mimeData = new QMimeData; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << m_materialToDrag.internalId(); + mimeData->setData(Constants::MIME_TYPE_MATERIAL, data); + mimeData->removeFormat("text/plain"); + + model->startDrag(mimeData, m_previewImageProvider->requestPixmap( + QString::number(m_materialToDrag.internalId()), nullptr, {128, 128})); + m_materialToDrag = {}; + } + } } return QObject::eventFilter(obj, event); @@ -166,6 +188,12 @@ void MaterialBrowserWidget::handleSearchfilterChanged(const QString &filterText) } } +void MaterialBrowserWidget::startDragMaterial(int index, const QPointF &mousePos) +{ + m_materialToDrag = m_materialBrowserModel->materialAt(index); + m_dragStartPoint = mousePos.toPoint(); +} + QString MaterialBrowserWidget::qmlSourcesPath() { #ifdef SHARE_QML_PATH diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h index f5f737007e7..30f9d05b509 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h @@ -69,6 +69,7 @@ public: void updateMaterialPreview(const ModelNode &node, const QPixmap &pixmap); Q_INVOKABLE void handleSearchfilterChanged(const QString &filterText); + Q_INVOKABLE void startDragMaterial(int index, const QPointF &mousePos); QQuickWidget *quickWidget() const; @@ -86,6 +87,9 @@ private: PreviewImageProvider *m_previewImageProvider = nullptr; QString m_filterText; + + ModelNode m_materialToDrag; + QPoint m_dragStartPoint; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp index 00987711c56..178f79e18a3 100644 --- a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp +++ b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp @@ -56,6 +56,8 @@ ChooseFromPropertyListFilter::ChooseFromPropertyListFilter(const NodeMetaInfo &i // ParticleAbstractShape3D // -> ParticleEmitter3D // -> Attractor3D + // Material + // -> Model const TypeName textureType = "QtQuick3D.Texture"; if (insertInfo.isSubclassOf(textureType)) { @@ -104,6 +106,9 @@ ChooseFromPropertyListFilter::ChooseFromPropertyListFilter(const NodeMetaInfo &i if (parentInfo.isSubclassOf("QtQuick3D.Particles3D.ParticleEmitter3D") || parentInfo.isSubclassOf("QtQuick3D.Particles3D.Attractor3D")) propertyList.append("shape"); + } else if (insertInfo.isSubclassOf("QtQuick3D.Material")) { + if (parentInfo.isSubclassOf("QtQuick3D.Particles3D.Model")) + propertyList.append("materials"); } } diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index 38c32f65cd8..d347854f004 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -464,6 +464,7 @@ QStringList NavigatorTreeModel::mimeTypes() const { const static QStringList types({Constants::MIME_TYPE_MODELNODE_LIST, Constants::MIME_TYPE_ITEM_LIBRARY_INFO, + Constants::MIME_TYPE_MATERIAL, Constants::MIME_TYPE_ASSETS}); return types; @@ -559,6 +560,8 @@ bool NavigatorTreeModel::dropMimeData(const QMimeData *mimeData, if (dropModelIndex.model() == this) { if (mimeData->hasFormat(Constants::MIME_TYPE_ITEM_LIBRARY_INFO)) { handleItemLibraryItemDrop(mimeData, rowNumber, dropModelIndex); + } else if (mimeData->hasFormat(Constants::MIME_TYPE_MATERIAL)) { + handleMaterialDrop(mimeData, rowNumber, dropModelIndex); } else if (mimeData->hasFormat(Constants::MIME_TYPE_ASSETS)) { const QStringList assetsPaths = QString::fromUtf8(mimeData->data(Constants::MIME_TYPE_ASSETS)).split(','); NodeAbstractProperty targetProperty; @@ -779,6 +782,33 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in } } +void NavigatorTreeModel::handleMaterialDrop(const QMimeData *mimeData, int rowNumber, const QModelIndex &dropModelIndex) +{ + QTC_ASSERT(m_view, return); + + const QModelIndex rowModelIndex = dropModelIndex.sibling(dropModelIndex.row(), 0); + int targetRowNumber = rowNumber; + NodeAbstractProperty targetProperty; + + bool foundTarget = findTargetProperty(rowModelIndex, this, &targetProperty, &targetRowNumber, "materials"); + if (!foundTarget) + return; + + ModelNode targetNode = targetProperty.parentModelNode(); + if (!targetNode.isSubclassOf("QtQuick3D.Model")) + return; + + QByteArray data = mimeData->data(Constants::MIME_TYPE_MATERIAL); + QDataStream stream(data); + qint32 internalId; + stream >> internalId; + ModelNode matNode = m_view->modelNodeForInternalId(internalId); + + m_view->executeInTransaction(__FUNCTION__, [&] { + m_view->assignMaterialTo3dModel(targetNode, matNode); + }); +} + ModelNode NavigatorTreeModel::handleItemLibraryImageDrop(const QString &imagePath, NodeAbstractProperty targetProperty, const QModelIndex &rowModelIndex, diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h index 96529816076..153491bfc14 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h @@ -115,6 +115,7 @@ private: int targetIndex, bool executeInTransaction = true); void handleInternalDrop(const QMimeData *mimeData, int rowNumber, const QModelIndex &dropModelIndex); void handleItemLibraryItemDrop(const QMimeData *mimeData, int rowNumber, const QModelIndex &dropModelIndex); + void handleMaterialDrop(const QMimeData *mimeData, int rowNumber, const QModelIndex &dropModelIndex); ModelNode handleItemLibraryImageDrop(const QString &imagePath, NodeAbstractProperty targetProperty, const QModelIndex &rowModelIndex, bool &outMoveNodesAfter); ModelNode handleItemLibraryFontDrop(const QString &fontFamily, NodeAbstractProperty targetProperty, diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp index 495982c6fc1..f8d5c884fbd 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp @@ -276,6 +276,15 @@ void NavigatorView::dragStarted(QMimeData *mimeData) m_widget->setDragType(itemLibraryEntry.typeName()); m_widget->update(); + } else if (mimeData->hasFormat(Constants::MIME_TYPE_MATERIAL)) { + QByteArray data = mimeData->data(Constants::MIME_TYPE_MATERIAL); + QDataStream stream(data); + qint32 internalId; + stream >> internalId; + ModelNode matNode = modelNodeForInternalId(internalId); + + m_widget->setDragType(matNode.metaInfo().typeName()); + m_widget->update(); } } diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index b2ed51cd874..486faeaa267 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -92,6 +92,7 @@ const char MATERIAL_LIB_ID[] = "__materialLibrary__"; const char MIME_TYPE_ITEM_LIBRARY_INFO[] = "application/vnd.qtdesignstudio.itemlibraryinfo"; const char MIME_TYPE_ASSETS[] = "application/vnd.qtdesignstudio.assets"; +const char MIME_TYPE_MATERIAL[] = "application/vnd.qtdesignstudio.material"; const char MIME_TYPE_ASSET_IMAGE[] = "application/vnd.qtdesignstudio.asset.image"; const char MIME_TYPE_ASSET_FONT[] = "application/vnd.qtdesignstudio.asset.font"; const char MIME_TYPE_ASSET_SHADER[] = "application/vnd.qtdesignstudio.asset.shader";