From f09d4538e73dfd86e748d62fe8dba56c5a716cc7 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Fri, 18 Mar 2022 17:28:28 +0200 Subject: [PATCH 01/11] QmlDesigner: Implement Material Editor Task-number: QDS-6438 Task-number: QDS-6439 Change-Id: I04e899a68aea665f0df8b65e21523632174ec76b Reviewed-by: Miikka Heikkinen Reviewed-by: Reviewed-by: Samuel Ghinet Reviewed-by: Thomas Hartmann --- .../requestmodelnodepreviewimagecommand.h | 14 + .../qtcreator/qml/qmlpuppet/editor3d_qt5.qrc | 1 + .../qtcreator/qml/qmlpuppet/editor3d_qt6.qrc | 1 + .../qmlpuppet/mockfiles/images/floor_tex.png | Bin 0 -> 1745 bytes .../mockfiles/qt5/MaterialNodeView.qml | 34 +- .../mockfiles/qt6/MaterialNodeView.qml | 34 +- .../qt5informationnodeinstanceserver.cpp | 53 +- .../qt5informationnodeinstanceserver.h | 6 +- .../itemLibraryQmlSources/Assets.qml | 4 +- .../itemLibraryQmlSources/ItemsView.qml | 9 +- .../MaterialBrowser.qml | 221 +++++ .../materialBrowserQmlSource/MaterialItem.qml | 134 +++ .../EmptyMaterialEditorPane.qml | 62 ++ .../MaterialEditorPane.qml | 55 ++ .../MaterialEditorToolBar.qml | 91 ++ .../MaterialEditorTopSection.qml | 125 +++ .../imports/HelperWidgets/ComboBox.qml | 23 + .../imports/HelperWidgets/IconButton.qml} | 62 +- .../imports/HelperWidgets}/SearchBox.qml | 4 +- .../imports/HelperWidgets/qmldir | 2 + .../imports/StudioControls/ComboBox.qml | 4 +- .../imports/StudioTheme/InternalConstants.qml | 243 +++--- .../imports/StudioTheme/Values.qml | 12 +- .../imports/StudioTheme/icons.ttf | Bin 22240 -> 23272 bytes src/plugins/qmldesigner/CMakeLists.txt | 17 + .../componentcore/componentcore_constants.h | 2 + .../componentcore/designeractionmanager.cpp | 11 + .../modelnodecontextmenu_helper.h | 19 + .../componentcore/modelnodeoperations.cpp | 32 + .../componentcore/modelnodeoperations.h | 1 + .../components/componentcore/theme.h | 5 + .../components/edit3d/edit3dcanvas.cpp | 43 +- .../components/edit3d/edit3dview.cpp | 11 +- .../materialbrowser/materialbrowsermodel.cpp | 271 ++++++ .../materialbrowser/materialbrowsermodel.h | 94 ++ .../materialbrowser/materialbrowserview.cpp | 266 ++++++ .../materialbrowser/materialbrowserview.h | 79 ++ .../materialbrowser/materialbrowserwidget.cpp | 205 +++++ .../materialbrowser/materialbrowserwidget.h | 91 ++ .../materialeditorcontextobject.cpp | 378 ++++++++ .../materialeditorcontextobject.h | 157 ++++ .../materialeditorqmlbackend.cpp | 346 ++++++++ .../materialeditor/materialeditorqmlbackend.h | 93 ++ .../materialeditortransaction.cpp | 73 ++ .../materialeditortransaction.h | 53 ++ .../materialeditor/materialeditorview.cpp | 817 ++++++++++++++++++ .../materialeditor/materialeditorview.h | 125 +++ .../navigator/navigatortreemodel.cpp | 106 ++- .../components/navigator/navigatortreemodel.h | 1 + .../propertyeditor/propertyeditorvalue.cpp | 41 +- .../propertyeditor/propertyeditorvalue.h | 2 + .../qmldesigner/designercore/include/model.h | 1 + .../qmldesigner/designercore/model/model.cpp | 7 + .../designercore/model/viewmanager.cpp | 10 +- src/plugins/qmldesigner/designmodewidget.cpp | 4 +- src/plugins/qmldesigner/designmodewidget.h | 7 +- .../qmldesigner/qmldesignerconstants.h | 1 + src/plugins/qmldesigner/qmldesignerplugin.qbs | 16 +- 58 files changed, 4329 insertions(+), 250 deletions(-) create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/images/floor_tex.png create mode 100644 share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml create mode 100644 share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml create mode 100644 share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml create mode 100644 share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorPane.qml create mode 100644 share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml create mode 100644 share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml rename share/qtcreator/qmldesigner/{itemLibraryQmlSources/PlusButton.qml => propertyEditorQmlSources/imports/HelperWidgets/IconButton.qml} (59%) rename share/qtcreator/qmldesigner/{itemLibraryQmlSources => propertyEditorQmlSources/imports/HelperWidgets}/SearchBox.qml (98%) create mode 100644 src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp create mode 100644 src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h create mode 100644 src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp create mode 100644 src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h create mode 100644 src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp create mode 100644 src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h create mode 100644 src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp create mode 100644 src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h create mode 100644 src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp create mode 100644 src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h create mode 100644 src/plugins/qmldesigner/components/materialeditor/materialeditortransaction.cpp create mode 100644 src/plugins/qmldesigner/components/materialeditor/materialeditortransaction.h create mode 100644 src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp create mode 100644 src/plugins/qmldesigner/components/materialeditor/materialeditorview.h diff --git a/share/qtcreator/qml/qmlpuppet/commands/requestmodelnodepreviewimagecommand.h b/share/qtcreator/qml/qmlpuppet/commands/requestmodelnodepreviewimagecommand.h index 91ef0eddbaa..773656048cd 100644 --- a/share/qtcreator/qml/qmlpuppet/commands/requestmodelnodepreviewimagecommand.h +++ b/share/qtcreator/qml/qmlpuppet/commands/requestmodelnodepreviewimagecommand.h @@ -55,6 +55,20 @@ private: qint32 m_renderItemId; }; +inline bool operator==(const RequestModelNodePreviewImageCommand &first, + const RequestModelNodePreviewImageCommand &second) +{ + return first.instanceId() == second.instanceId() + && first.size() == second.size() + && first.componentPath() == second.componentPath() + && first.renderItemId() == second.renderItemId(); +} + +inline size_t qHash(const RequestModelNodePreviewImageCommand &key, size_t seed) +{ + return qHashMulti(seed, key.instanceId(), key.size(), key.componentPath(), key.renderItemId()); +} + QDataStream &operator<<(QDataStream &out, const RequestModelNodePreviewImageCommand &command); QDataStream &operator>>(QDataStream &in, RequestModelNodePreviewImageCommand &command); diff --git a/share/qtcreator/qml/qmlpuppet/editor3d_qt5.qrc b/share/qtcreator/qml/qmlpuppet/editor3d_qt5.qrc index 6f1aa104f93..7e023c127b7 100644 --- a/share/qtcreator/qml/qmlpuppet/editor3d_qt5.qrc +++ b/share/qtcreator/qml/qmlpuppet/editor3d_qt5.qrc @@ -13,6 +13,7 @@ mockfiles/images/directional@2x.png mockfiles/images/point.png mockfiles/images/point@2x.png + mockfiles/images/floor_tex.png mockfiles/images/spot.png mockfiles/images/spot@2x.png mockfiles/qt5/AdjustableArrow.qml diff --git a/share/qtcreator/qml/qmlpuppet/editor3d_qt6.qrc b/share/qtcreator/qml/qmlpuppet/editor3d_qt6.qrc index 9831bec4be2..e8d66548c32 100644 --- a/share/qtcreator/qml/qmlpuppet/editor3d_qt6.qrc +++ b/share/qtcreator/qml/qmlpuppet/editor3d_qt6.qrc @@ -15,6 +15,7 @@ mockfiles/images/directional@2x.png mockfiles/images/point.png mockfiles/images/point@2x.png + mockfiles/images/floor_tex.png mockfiles/images/spot.png mockfiles/images/spot@2x.png mockfiles/qt6/AdjustableArrow.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/images/floor_tex.png b/share/qtcreator/qml/qmlpuppet/mockfiles/images/floor_tex.png new file mode 100644 index 0000000000000000000000000000000000000000..d8550f8044975ad6db8736d3183df99bf2d12ab8 GIT binary patch literal 1745 zcmeAS@N?(olHy`uVBq!ia0y~yV6XvU4kiW$hOP7bR2di;I14-?iy0WWg+Z8+Vb&Z8 z1_ssxnIRD+5xzcF$@#f@i7EL>sd^Q;1q>iyV_#8_n4FzjqL7rDo|$K>^nUk#C56ls zTcvPQUjyF)=hTc$kE){7;3~h6MhI|Z8xtBTx$+|-gpg^Jvqyke^gTP3i$ zR(Zu%AYpwa1+bEmY+EHqkcA2nz5xo(`9-M;=6Z&@$p!`n3g(u2Nd{>aNvW1P3PuJ- zhWds^`UV!dhL%=_hE_&K3Q(YAr(jc*l4cd;;s&*>C?(BSDWjyMz)D}gyu4hm+*mKa zC|%#s($Z4jz)0W7NVg~@O}Dr*uOzWTH?LS3W`av%TiO6pn_N(OjMLX z3@y({%|lOdD9H$-tT;cdq&%@G)iFIauY_=lhG-P6S}B%|@| zjf1=`20Si->SY-%eJ}OaMo8?)Wj#4PY*ze%7bg|?w!GezQ~GY+%;IxrO1?k+UHemM z!i5m0U`}6Eld0Gk8z0Bd-u?aF{O2oIJ$;-eHgjjqmAmhM_jP=)EtgS!Hs`ea>a3o^ zdCw)1k8j)_vujmUwqT*maj`Fb$J5jHmshd>(%&zK$8@LQT1_RB)z6>q231%Lp00i_ I>zopr029= QT_VERSION_CHECK(6, 1, 0) + RequestModelNodePreviewImageCommand cmd = *m_modelNodePreviewImageCommands.begin(); ServerNodeInstance instance; - if (m_modelNodePreviewImageCommand.renderItemId() >= 0) - instance = instanceForId(m_modelNodePreviewImageCommand.renderItemId()); + if (cmd.renderItemId() >= 0) + instance = instanceForId(cmd.renderItemId()); else - instance = instanceForId(m_modelNodePreviewImageCommand.instanceId()); + instance = instanceForId(cmd.instanceId()); if (instance.isSubclassOf("QQuick3DObject")) - doRenderModelNode3DImageView(); + doRenderModelNode3DImageView(cmd); else if (instance.isSubclassOf("QQuickItem")) - doRenderModelNode2DImageView(); + doRenderModelNode2DImageView(cmd); + + m_modelNodePreviewImageCommands.remove(cmd); + if (!m_modelNodePreviewImageCommands.isEmpty()) + m_renderModelNodeImageViewTimer.start(17); #endif } -void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView() +void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView(const RequestModelNodePreviewImageCommand &cmd) { #ifdef QUICK3D_MODULE if (m_modelNode3DImageViewData.rootItem) { @@ -1059,19 +1064,19 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView() m_modelNode3DImageViewData.contentItem = getContentItemForRendering(m_modelNode3DImageViewData.rootItem); QImage renderImage; - if (m_modelNodePreviewImageCache.contains(m_modelNodePreviewImageCommand.componentPath())) { - renderImage = m_modelNodePreviewImageCache[m_modelNodePreviewImageCommand.componentPath()]; + if (m_modelNodePreviewImageCache.contains(cmd.componentPath())) { + renderImage = m_modelNodePreviewImageCache[cmd.componentPath()]; } else { bool createdFromComponent = false; QObject *instanceObj = nullptr; - ServerNodeInstance instance = instanceForId(m_modelNodePreviewImageCommand.instanceId()); - if (!m_modelNodePreviewImageCommand.componentPath().isEmpty() + ServerNodeInstance instance = instanceForId(cmd.instanceId()); + if (!cmd.componentPath().isEmpty() && instance.isSubclassOf("QQuick3DNode")) { // Create a new instance for Node components, as using Nodes in multiple // import scenes simultaneously isn't supported. And even if it was, we still // wouldn't want the children of the Node to appear in the preview. QQmlComponent component(engine()); - component.loadUrl(QUrl::fromLocalFile(m_modelNodePreviewImageCommand.componentPath())); + component.loadUrl(QUrl::fromLocalFile(cmd.componentPath())); instanceObj = qobject_cast(component.create()); if (!instanceObj) { qWarning() << "Could not create preview component: " << component.errors(); @@ -1081,7 +1086,7 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView() } else { instanceObj = instance.internalObject(); } - QSize renderSize = m_modelNodePreviewImageCommand.size(); + QSize renderSize = cmd.size(); if (Internal::QuickItemNodeInstance::unifiedRenderPathOrQt6()) { // Requested size is already adjusted for target pixel ratio, so we have to adjust // back if ratio is not default for our window. @@ -1138,13 +1143,13 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView() if (createdFromComponent) { // If component changes, puppet will need a reset anyway, so we can cache the image - m_modelNodePreviewImageCache.insert(m_modelNodePreviewImageCommand.componentPath(), + m_modelNodePreviewImageCache.insert(cmd.componentPath(), renderImage); delete instanceObj; } } // Key number is selected so that it is unlikely to conflict other ImageContainer use. - ImageContainer imgContainer(m_modelNodePreviewImageCommand.instanceId(), {}, 2100000001); + ImageContainer imgContainer(cmd.instanceId(), {}, 2100000001 + cmd.instanceId()); imgContainer.setImage(renderImage); // send the rendered image to creator process @@ -1175,23 +1180,23 @@ static QRectF itemBoundingRect(QQuickItem *item) return itemRect; } -void Qt5InformationNodeInstanceServer::doRenderModelNode2DImageView() +void Qt5InformationNodeInstanceServer::doRenderModelNode2DImageView(const RequestModelNodePreviewImageCommand &cmd) { if (m_modelNode2DImageViewData.rootItem) { if (!m_modelNode2DImageViewData.contentItem) m_modelNode2DImageViewData.contentItem = getContentItemForRendering(m_modelNode2DImageViewData.rootItem); // Key number is the same as in 3D case as they produce image for same purpose - auto imgContainer = ImageContainer(m_modelNodePreviewImageCommand.instanceId(), {}, 2100000001); + auto imgContainer = ImageContainer(cmd.instanceId(), {}, 2100000001 + cmd.instanceId()); QImage renderImage; - if (m_modelNodePreviewImageCache.contains(m_modelNodePreviewImageCommand.componentPath())) { - renderImage = m_modelNodePreviewImageCache[m_modelNodePreviewImageCommand.componentPath()]; + if (m_modelNodePreviewImageCache.contains(cmd.componentPath())) { + renderImage = m_modelNodePreviewImageCache[cmd.componentPath()]; } else { QQuickItem *instanceItem = nullptr; - if (!m_modelNodePreviewImageCommand.componentPath().isEmpty()) { + if (!cmd.componentPath().isEmpty()) { QQmlComponent component(engine()); - component.loadUrl(QUrl::fromLocalFile(m_modelNodePreviewImageCommand.componentPath())); + component.loadUrl(QUrl::fromLocalFile(cmd.componentPath())); instanceItem = qobject_cast(component.create()); if (!instanceItem) { qWarning() << "Could not create preview component: " << component.errors(); @@ -1207,7 +1212,7 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode2DImageView() // Some component may expect to always be shown at certain size, so their layouts may // not support scaling, so let's always render at the default size if item has one and // scale the resulting image instead. - QSize finalSize = m_modelNodePreviewImageCommand.size(); + QSize finalSize = cmd.size(); QRectF renderRect = itemBoundingRect(instanceItem); QSize renderSize = renderRect.size().toSize(); if (renderSize.isEmpty()) { @@ -1247,7 +1252,7 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode2DImageView() delete instanceItem; // If component changes, puppet will need a reset anyway, so we can cache the image - m_modelNodePreviewImageCache.insert(m_modelNodePreviewImageCommand.componentPath(), renderImage); + m_modelNodePreviewImageCache.insert(cmd.componentPath(), renderImage); } if (!renderImage.isNull()) { @@ -2229,7 +2234,7 @@ void Qt5InformationNodeInstanceServer::view3DAction(const View3DActionCommand &c void Qt5InformationNodeInstanceServer::requestModelNodePreviewImage(const RequestModelNodePreviewImageCommand &command) { - m_modelNodePreviewImageCommand = command; + m_modelNodePreviewImageCommands.insert(command); renderModelNodeImageView(); } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h index a86ce2a4615..b6375076b66 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h @@ -139,8 +139,8 @@ private: void doRender3DEditView(); void renderModelNodeImageView(); void doRenderModelNodeImageView(); - void doRenderModelNode3DImageView(); - void doRenderModelNode2DImageView(); + void doRenderModelNode3DImageView(const RequestModelNodePreviewImageCommand &cmd); + void doRenderModelNode2DImageView(const RequestModelNodePreviewImageCommand &cmd); void updateLockedAndHiddenStates(const QSet &instances); void handleInputEvents(); void resolveImportSupport(); @@ -159,7 +159,7 @@ private: RenderViewData m_modelNode2DImageViewData; bool m_editView3DSetupDone = false; - RequestModelNodePreviewImageCommand m_modelNodePreviewImageCommand; + QSet m_modelNodePreviewImageCommands; QHash m_modelNodePreviewImageCache; QSet m_view3Ds; QMultiHash m_3DSceneMap; // key: scene root, value: node diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml index a90dfa50f19..a8d0a53381b 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml @@ -442,10 +442,12 @@ Item { width: parent.width - addAssetButton.width - 5 } - PlusButton { + IconButton { id: addAssetButton anchors.verticalCenter: parent.verticalCenter tooltip: qsTr("Add a new asset to the project.") + icon: StudioTheme.Constants.plus + buttonSize: parent.height onClicked: rootView.handleAddAsset() } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemsView.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemsView.qml index bc0dc79ef25..4771127ab82 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemsView.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemsView.qml @@ -218,12 +218,15 @@ Item { SearchBox { id: searchBox - width: parent.width - addAssetButton.width - 5 + width: parent.width - addModuleButton.width - 5 } - PlusButton { - id: addAssetButton + IconButton { + id: addModuleButton + anchors.verticalCenter: parent.verticalCenter tooltip: qsTr("Add a module.") + icon: StudioTheme.Constants.plus + buttonSize: parent.height onClicked: isAddModuleView = true } diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml new file mode 100644 index 00000000000..3fdc9cffdac --- /dev/null +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -0,0 +1,221 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuickDesignerTheme 1.0 +import HelperWidgets 2.0 +import StudioControls 1.0 as StudioControls +import StudioTheme 1.0 as StudioTheme + +Item { + id: root + + readonly property int cellWidth: 100 + readonly property int cellHeight: 120 + + property var currentMaterial: null + property int currentMaterialIdx: 0 + + // Called also from C++ to close context menu on focus out + function closeContextMenu() + { + contextMenu.close() + } + + // Called from C++ to refresh a preview material after it changes + function refreshPreview(idx) + { + var item = gridRepeater.itemAt(idx); + if (item) + item.refreshPreview(); + } + + // Called from C++ + function clearSearchFilter() + { + searchBox.clearSearchFilter(); + } + + MouseArea { + id: rootMouseArea + + anchors.fill: parent + + acceptedButtons: Qt.RightButton + + onClicked: { + root.currentMaterial = null + contextMenu.popup() + } + } + + Connections { + target: materialBrowserModel + + function onSelectedIndexChanged() { + // commit rename upon changing selection + var item = gridRepeater.itemAt(currentMaterialIdx); + if (item) + item.commitRename(); + + currentMaterialIdx = materialBrowserModel.selectedIndex; + } + } + + StudioControls.Menu { + id: contextMenu + + closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside + + StudioControls.MenuItem { + text: qsTr("Apply to selected (replace)") + enabled: currentMaterial + onTriggered: materialBrowserModel.applyToSelected(currentMaterial.materialInternalId, false) + } + + StudioControls.MenuItem { + text: qsTr("Apply to selected (add)") + enabled: currentMaterial + onTriggered: materialBrowserModel.applyToSelected(currentMaterial.materialInternalId, true) + } + + StudioControls.MenuItem { + text: qsTr("Rename") + enabled: currentMaterial + onTriggered: { + var item = gridRepeater.itemAt(currentMaterialIdx); + if (item) + item.startRename(); + } + } + + StudioControls.MenuItem { + text: qsTr("Delete") + enabled: currentMaterial + + onTriggered: materialBrowserModel.deleteMaterial(currentMaterial.materialInternalId) + } + + StudioControls.MenuSeparator {} + + StudioControls.MenuItem { + text: qsTr("New Material") + + onTriggered: materialBrowserModel.addNewMaterial() + } + } + + Column { + id: col + y: 5 + spacing: 5 + + Row { + width: root.width + + SearchBox { + id: searchBox + + width: root.width - addMaterialButton.width + } + + IconButton { + id: addMaterialButton + + tooltip: qsTr("Add a material.") + + icon: StudioTheme.Constants.plus + anchors.verticalCenter: parent.verticalCenter + buttonSize: searchBox.height + onClicked: materialBrowserModel.addNewMaterial() + } + } + + Text { + text: qsTr("No match found."); + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.baseFontSize + leftPadding: 10 + visible: materialBrowserModel.hasQuick3DImport && materialBrowserModel.isEmpty && !searchBox.isEmpty() + } + + Text { + text: qsTr("No materials yet."); + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.mediumFontSize + topPadding: 30 + anchors.horizontalCenter: parent.horizontalCenter + visible: materialBrowserModel.hasQuick3DImport && materialBrowserModel.isEmpty && searchBox.isEmpty() + } + + Text { + text: qsTr("Add QtQuick3D module using the Components view to enable the Material Browser."); + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.mediumFontSize + topPadding: 30 + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + width: root.width + anchors.horizontalCenter: parent.horizontalCenter + visible: !materialBrowserModel.hasQuick3DImport + } + + ScrollView { + id: scrollView + + width: root.width + height: root.height - searchBox.height + clip: true + + Grid { + id: grid + + width: scrollView.width + leftPadding: 5 + rightPadding: 5 + bottomPadding: 5 + columns: root.width / root.cellWidth + + Repeater { + id: gridRepeater + + model: materialBrowserModel + delegate: MaterialItem { + width: root.cellWidth + height: root.cellHeight + + onShowContextMenu: { + if (searchBox.isEmpty()) { + root.currentMaterial = model + contextMenu.popup() + } + } + } + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml new file mode 100644 index 00000000000..4eeb120b27c --- /dev/null +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuickDesignerTheme 1.0 +import HelperWidgets 2.0 +import StudioTheme 1.0 as StudioTheme + +Rectangle { + id: root + + signal showContextMenu() + + function refreshPreview() + { + img.source = "" + img.source = "image://materialBrowser/" + materialInternalId + } + + function startRename() + { + matName.readOnly = false + matName.selectAll() + matName.forceActiveFocus() + nameMouseArea.enabled = false + } + + function commitRename() + { + if (matName.readOnly) + return; + + matName.readOnly = true + nameMouseArea.enabled = true + + materialBrowserModel.renameMaterial(index, matName.text); + } + + border.width: materialBrowserModel.selectedIndex === index ? 1 : 0 + border.color: materialBrowserModel.selectedIndex === index + ? StudioTheme.Values.themeControlOutlineInteraction + : "transparent" + color: "transparent" + visible: materialVisible + + MouseArea { + id: mouseArea + + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onClicked: (mouse) => { + materialBrowserModel.selectMaterial(index) + if (mouse.button === Qt.RightButton) + root.showContextMenu() + } + + onDoubleClicked: materialBrowserModel.openMaterialEditor(); + } + + Column { + anchors.fill: parent + spacing: 1 + + Item { width: 1; height: 5 } // spacer + + Image { + id: img + + width: root.width - 10 + height: img.width + anchors.horizontalCenter: parent.horizontalCenter + source: "image://materialBrowser/" + materialInternalId + cache: false + } + + TextInput { + id: matName + + text: materialName + + width: img.width + clip: true + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: TextInput.AlignHCenter + + font.pixelSize: StudioTheme.Values.myFontSize + + readOnly: true + selectByMouse: !matName.readOnly + + color: StudioTheme.Values.themeTextColor + selectionColor: StudioTheme.Values.themeTextSelectionColor + selectedTextColor: StudioTheme.Values.themeTextSelectedTextColor + + // allow only alphanumeric characters, underscores, no space at start, and 1 space between words + validator: RegExpValidator { regExp: /^(\w+\s)*\w+$/ } + + onEditingFinished: root.commitRename() + + MouseArea { + id: nameMouseArea + + anchors.fill: parent + + onClicked: materialBrowserModel.selectMaterial(index) + onDoubleClicked: root.startRename() + } + } + } +} diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml new file mode 100644 index 00000000000..2e812dcd661 --- /dev/null +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuickDesignerTheme 1.0 +import HelperWidgets 2.0 +import StudioTheme 1.0 as StudioTheme + +PropertyEditorPane { + id: root + + signal toolBarAction(int action) + + Column { + id: col + + MaterialEditorToolBar { + width: root.width + + onToolBarAction: (action) => root.toolBarAction(action) + } + + Item { + width: root.width - 2 * col.padding + height: 150 + + Text { + text: hasQuick3DImport ? qsTr("No materials yet.\nClick 'Add new material' above to start.") + : qsTr("Add QtQuick3D module using the Components view to enable the Material Editor.") + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.baseFontSize + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + width: root.width + anchors.centerIn: parent + } + } + } +} diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorPane.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorPane.qml new file mode 100644 index 00000000000..cfc037bc24f --- /dev/null +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorPane.qml @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +import QtQuick 2.15 +import QtQuickDesignerTheme 1.0 +import HelperWidgets 2.0 + +PropertyEditorPane { + id: itemPane + + signal toolBarAction(int action) + + // invoked from C++ to refresh material preview image + function refreshPreview() + { + topSection.refreshPreview() + } + + MaterialEditorTopSection { + id: topSection + + onToolBarAction: (action) => itemPane.toolBarAction(action) + } + + Item { width: 1; height: 10 } + + Loader { + id: specificsOne + anchors.left: parent.left + anchors.right: parent.right + source: specificsUrl + } +} diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml new file mode 100644 index 00000000000..028957c882f --- /dev/null +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +import QtQuick 2.15 +import QtQuickDesignerTheme 1.0 +import HelperWidgets 2.0 +import StudioTheme 1.0 as StudioTheme +import ToolBarAction 1.0 + +Rectangle { + id: root + + color: StudioTheme.Values.themeSectionHeadBackground + width: row.width + height: 40 + + signal toolBarAction(int action) + + Row { + id: row + + anchors.verticalCenter: parent.verticalCenter + leftPadding: 6 + + IconButton { + icon: StudioTheme.Constants.applyMaterialToSelected + + normalColor: "transparent" + iconSize: StudioTheme.Values.bigIconFontSize + buttonSize: root.height + enabled: hasMaterial && hasQuick3DImport + onClicked: root.toolBarAction(ToolBarAction.ApplyToSelected) + tooltip: qsTr("Apply material to selected model.") + } + + IconButton { + icon: StudioTheme.Constants.newMaterial + + normalColor: "transparent" + iconSize: StudioTheme.Values.bigIconFontSize + buttonSize: root.height + enabled: hasQuick3DImport + onClicked: root.toolBarAction(ToolBarAction.AddNewMaterial) + tooltip: qsTr("Add a new material.") + } + + IconButton { + icon: StudioTheme.Constants.deleteMaterial + + normalColor: "transparent" + iconSize: StudioTheme.Values.bigIconFontSize + buttonSize: root.height + enabled: hasMaterial && hasQuick3DImport + onClicked: root.toolBarAction(ToolBarAction.DeleteCurrentMaterial) + tooltip: qsTr("Delete current material.") + } + + IconButton { + icon: StudioTheme.Constants.openMaterialBrowser + + normalColor: "transparent" + iconSize: StudioTheme.Values.bigIconFontSize + buttonSize: root.height + enabled: hasMaterial && hasQuick3DImport + onClicked: root.toolBarAction(ToolBarAction.OpenMaterialBrowser) + tooltip: qsTr("Open material browser.") + } + } +} diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml new file mode 100644 index 00000000000..2e21d5814ac --- /dev/null +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuickDesignerTheme 1.0 +import QtQuick.Templates 2.15 as T +import HelperWidgets 2.0 +import StudioTheme 1.0 as StudioTheme + +Column { + id: root + + signal toolBarAction(int action) + + function refreshPreview() + { + materialPreview.source = "" + materialPreview.source = "image://materialEditor/preview" + } + + anchors.left: parent.left + anchors.right: parent.right + + MaterialEditorToolBar { + width: root.width + + onToolBarAction: (action) => root.toolBarAction(action) + } + + Item { width: 1; height: 10 } // spacer + + Rectangle { + width: 152 + height: 152 + color: "#000000" + anchors.horizontalCenter: parent.horizontalCenter + + Image { + id: materialPreview + width: 150 + height: 150 + anchors.centerIn: parent + source: "image://materialEditor/preview" + cache: false + } + } + + Section { + // Section with hidden header is used so properties are aligned with the other sections' properties + hideHeader: true + width: parent.width + + SectionLayout { + PropertyLabel { text: qsTr("Name") } + + SecondColumnLayout { + Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth } + + LineEdit { + implicitWidth: StudioTheme.Values.singleControlColumnWidth + width: StudioTheme.Values.singleControlColumnWidth + backendValue: backendValues.objectName + placeholderText: qsTr("Material name") + + text: backendValues.id.value + showTranslateCheckBox: false + showExtendedFunctionButton: false + + // allow only alphanumeric characters, underscores, no space at start, and 1 space between words + validator: RegExpValidator { regExp: /^(\w+\s)*\w+$/ } + } + + ExpandingSpacer {} + } + + PropertyLabel { text: qsTr("Type") } + + SecondColumnLayout { + Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth } + + ComboBox { + currentIndex: { + if (backendValues.__classNamePrivateInternal.value === "CustomMaterial") + return 2 + + if (backendValues.__classNamePrivateInternal.value === "PrincipledMaterial") + return 1 + + return 0 + } + + model: ["DefaultMaterial", "PrincipledMaterial", "CustomMaterial"] + showExtendedFunctionButton: false + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + onActivated: changeTypeName(currentValue) + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ComboBox.qml index 1b2df5756b5..48ec9fa470a 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ComboBox.qml @@ -74,6 +74,29 @@ StudioControls.ComboBox { property alias colorLogic: colorLogic + DropArea { + id: dropArea + + anchors.fill: parent + + property string assetPath: "" + + onEntered: (drag) => { + dropArea.assetPath = drag.getDataAsString(drag.keys[0]).split(",")[0] + + drag.accepted = comboBox.backendValue !== undefined && comboBox.backendValue.isSupportedDrop(dropArea.assetPath) + comboBox.hasActiveDrag = drag.accepted + } + + onExited: comboBox.hasActiveDrag = false + + onDropped: { + comboBox.backendValue.commitDrop(dropArea.assetPath) + comboBox.hasActiveDrag = false + } + + } + ExtendedFunctionLogic { id: extFuncLogic backendValue: comboBox.backendValue diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/PlusButton.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconButton.qml similarity index 59% rename from share/qtcreator/qmldesigner/itemLibraryQmlSources/PlusButton.qml rename to share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconButton.qml index 31f69be9967..2fc10b18554 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/PlusButton.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/IconButton.qml @@ -26,54 +26,58 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuickDesignerTheme 1.0 -import HelperWidgets 2.0 as HelperWidgets -import StudioControls 1.0 as StudioControls import StudioTheme 1.0 as StudioTheme Rectangle { id: root - property string tooltip: "" - signal clicked() - implicitWidth: 29 - implicitHeight: 29 - color: mouseArea.containsMouse && enabled - ? StudioTheme.Values.themeControlBackgroundHover - : StudioTheme.Values.themeControlBackground + property alias icon: icon.text + property alias enabled: mouseArea.enabled + property alias tooltip: toolTip.text + property alias iconSize: icon.font.pixelSize + + property int buttonSize: StudioTheme.Values.height + property color normalColor: StudioTheme.Values.themeControlBackground + property color hoverColor: StudioTheme.Values.themeControlBackgroundHover + property color pressColor: StudioTheme.Values.themeControlBackgroundInteraction + + width: buttonSize + height: buttonSize + + color: mouseArea.pressed ? pressColor + : mouseArea.containsMouse ? hoverColor + : normalColor Behavior on color { ColorAnimation { - duration: StudioTheme.Values.hoverDuration - easing.type: StudioTheme.Values.hoverEasing + duration: 300 + easing.type: Easing.OutQuad } } - Label { // + sign - text: StudioTheme.Constants.plus + Text { + id: icon + + color: root.enabled ? StudioTheme.Values.themeTextColor : StudioTheme.Values.themeTextColorDisabled font.family: StudioTheme.Constants.iconFont.family - font.pixelSize: StudioTheme.Values.myIconFontSize - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - anchors.centerIn: parent - color: root.enabled ? StudioTheme.Values.themeIconColor - : StudioTheme.Values.themeIconColorDisabled - scale: mouseArea.containsMouse ? 1.4 : 1 - - Behavior on scale { - NumberAnimation { - duration: 300 - easing.type: Easing.OutExpo - } - } + font.pixelSize: StudioTheme.Values.baseIconFontSize + anchors.centerIn: root } - HelperWidgets.ToolTipArea { + MouseArea { id: mouseArea + anchors.fill: parent hoverEnabled: true onClicked: root.clicked() - tooltip: root.tooltip + } + + ToolTip { + id: toolTip + + visible: mouseArea.containsMouse + delay: 1000 } } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/SearchBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SearchBox.qml similarity index 98% rename from share/qtcreator/qmldesigner/itemLibraryQmlSources/SearchBox.qml rename to share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SearchBox.qml index 81ed5c11e49..d1b6001bcf6 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/SearchBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SearchBox.qml @@ -26,8 +26,6 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuickDesignerTheme 1.0 -import HelperWidgets 2.0 as HelperWidgets -import StudioControls 1.0 as StudioControls import StudioTheme 1.0 as StudioTheme Item { @@ -87,7 +85,7 @@ Item { Label { text: StudioTheme.Constants.search font.family: StudioTheme.Constants.iconFont.family - font.pixelSize: 16 + font.pixelSize: StudioTheme.Values.myIconFontSize anchors.left: parent.left anchors.leftMargin: 7 anchors.verticalCenter: parent.verticalCenter diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir index 8b87f1ca79f..9c1c873ceb8 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir @@ -37,6 +37,7 @@ GradientPropertySpinBox 2.0 GradientPropertySpinBox.qml HorizontalScrollBar 2.0 HorizontalScrollBar.qml HueSlider 2.0 HueSlider.qml IconIndicator 2.0 IconIndicator.qml +IconButton 2.0 IconButton.qml IconLabel 2.0 IconLabel.qml ImagePreviewTooltipArea 2.0 ImagePreviewTooltipArea.qml ImageSection 2.0 ImageSection.qml @@ -59,6 +60,7 @@ RoundedPanel 2.0 RoundedPanel.qml ScrollView 2.0 ScrollView.qml SecondColumnLayout 2.0 SecondColumnLayout.qml Section 2.0 Section.qml +SearchBox 2.0 SearchBox.qml SectionLayout 2.0 SectionLayout.qml Spacer 2.0 Spacer.qml SpinBox 2.0 SpinBox.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBox.qml index eb48ab6dc09..fbd4f530626 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ComboBox.qml @@ -39,6 +39,7 @@ T.ComboBox { && myComboBox.enabled property bool edit: myComboBox.activeFocus && myComboBox.editable property bool open: comboBoxPopup.opened + property bool hasActiveDrag: false property bool dirty: false // user modification flag @@ -251,7 +252,8 @@ T.ComboBox { PropertyChanges { target: comboBoxBackground color: StudioTheme.Values.themeControlBackground - border.color: StudioTheme.Values.themeControlOutline + border.color: hasActiveDrag ? StudioTheme.Values.themeInteraction + : StudioTheme.Values.themeControlOutline } }, // This state is intended for ComboBoxes which aren't editable, but have focus e.g. via diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml index db952d5286a..5972e56e04c 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml @@ -64,125 +64,130 @@ QtObject { readonly property string animatedProperty: "\u003B" readonly property string annotationBubble: "\u003C" readonly property string annotationDecal: "\u003D" - readonly property string assign: "\u003E" - readonly property string bevelAll: "\u003F" - readonly property string bevelCorner: "\u0040" - readonly property string centerHorizontal: "\u0041" - readonly property string centerVertical: "\u0042" - readonly property string closeCross: "\u0043" - readonly property string colorPopupClose: "\u0044" - readonly property string columnsAndRows: "\u0045" - readonly property string copyStyle: "\u0046" - readonly property string cornerA: "\u0047" - readonly property string cornerB: "\u0048" - readonly property string cornersAll: "\u0049" - readonly property string curveDesigner: "\u004A" - readonly property string curveEditor: "\u004B" - readonly property string decisionNode: "\u004C" - readonly property string deleteColumn: "\u004D" - readonly property string deleteRow: "\u004E" - readonly property string deleteTable: "\u004F" - readonly property string detach: "\u0050" - readonly property string distributeBottom: "\u0051" - readonly property string distributeCenterHorizontal: "\u0052" - readonly property string distributeCenterVertical: "\u0053" - readonly property string distributeLeft: "\u0054" - readonly property string distributeOriginBottomRight: "\u0055" - readonly property string distributeOriginCenter: "\u0056" - readonly property string distributeOriginNone: "\u0057" - readonly property string distributeOriginTopLeft: "\u0058" - readonly property string distributeRight: "\u0059" - readonly property string distributeSpacingHorizontal: "\u005A" - readonly property string distributeSpacingVertical: "\u005B" - readonly property string distributeTop: "\u005C" - readonly property string download: "\u005D" - readonly property string downloadUnavailable: "\u005E" - readonly property string downloadUpdate: "\u005F" - readonly property string downloaded: "\u0060" - readonly property string edit: "\u0061" - readonly property string eyeDropper: "\u0062" - readonly property string favorite: "\u0063" - readonly property string flowAction: "\u0064" - readonly property string flowTransition: "\u0065" - readonly property string fontStyleBold: "\u0066" - readonly property string fontStyleItalic: "\u0067" - readonly property string fontStyleStrikethrough: "\u0068" - readonly property string fontStyleUnderline: "\u0069" - readonly property string gradient: "\u006A" - readonly property string gridView: "\u006B" - readonly property string idAliasOff: "\u006C" - readonly property string idAliasOn: "\u006D" - readonly property string infinity: "\u006E" - readonly property string keyframe: "\u006F" - readonly property string linkTriangle: "\u0070" - readonly property string linked: "\u0071" - readonly property string listView: "\u0072" - readonly property string lockOff: "\u0073" - readonly property string lockOn: "\u0074" - readonly property string mergeCells: "\u0075" - readonly property string minus: "\u0076" - readonly property string mirror: "\u0077" - readonly property string orientation: "\u0078" - readonly property string paddingEdge: "\u0079" - readonly property string paddingFrame: "\u007A" - readonly property string pasteStyle: "\u007B" - readonly property string pause: "\u007C" - readonly property string pin: "\u007D" - readonly property string play: "\u007E" - readonly property string plus: "\u007F" - readonly property string promote: "\u0080" - readonly property string readOnly: "\u0081" - readonly property string redo: "\u0082" - readonly property string rotationFill: "\u0083" - readonly property string rotationOutline: "\u0084" - readonly property string search: "\u0085" - readonly property string sectionToggle: "\u0086" - readonly property string splitColumns: "\u0087" - readonly property string splitRows: "\u0088" - readonly property string startNode: "\u0089" - readonly property string testIcon: "\u008A" - readonly property string textAlignBottom: "\u008B" - readonly property string textAlignCenter: "\u008C" - readonly property string textAlignJustified: "\u008D" - readonly property string textAlignLeft: "\u008E" - readonly property string textAlignMiddle: "\u008F" - readonly property string textAlignRight: "\u0090" - readonly property string textAlignTop: "\u0091" - readonly property string textBulletList: "\u0092" - readonly property string textFullJustification: "\u0093" - readonly property string textNumberedList: "\u0094" - readonly property string tickIcon: "\u0095" - readonly property string translationCreateFiles: "\u0096" - readonly property string translationCreateReport: "\u0097" - readonly property string translationExport: "\u0098" - readonly property string translationImport: "\u0099" - readonly property string translationSelectLanguages: "\u009A" - readonly property string translationTest: "\u009B" - readonly property string transparent: "\u009D" - readonly property string triState: "\u009E" - readonly property string triangleArcA: "\u009F" - readonly property string triangleArcB: "\u00A0" - readonly property string triangleCornerA: "\u00A1" - readonly property string triangleCornerB: "\u00A2" - readonly property string unLinked: "\u00A3" - readonly property string undo: "\u00A4" - readonly property string unpin: "\u00A5" - readonly property string upDownIcon: "\u00A6" - readonly property string upDownSquare2: "\u00A7" - readonly property string visibilityOff: "\u00A8" - readonly property string visibilityOn: "\u00A9" - readonly property string wildcard: "\u00AA" - readonly property string wizardsAutomotive: "\u00AB" - readonly property string wizardsDesktop: "\u00AC" - readonly property string wizardsGeneric: "\u00AE" - readonly property string wizardsMcuEmpty: "\u00AF" - readonly property string wizardsMcuGraph: "\u00B0" - readonly property string wizardsMobile: "\u00B1" - readonly property string wizardsUnknown: "\u00B2" - readonly property string zoomAll: "\u00B3" - readonly property string zoomIn: "\u00B4" - readonly property string zoomOut: "\u00B5" - readonly property string zoomSelection: "\u00B6" + readonly property string applyMaterialToSelected: "\u003E" + readonly property string assign: "\u003F" + readonly property string bevelAll: "\u0040" + readonly property string bevelCorner: "\u0041" + readonly property string centerHorizontal: "\u0042" + readonly property string centerVertical: "\u0043" + readonly property string closeCross: "\u0044" + readonly property string colorPopupClose: "\u0045" + readonly property string columnsAndRows: "\u0046" + readonly property string copyStyle: "\u0047" + readonly property string cornerA: "\u0048" + readonly property string cornerB: "\u0049" + readonly property string cornersAll: "\u004A" + readonly property string curveDesigner: "\u004B" + readonly property string curveEditor: "\u004C" + readonly property string customMaterialEditor: "\u004D" + readonly property string decisionNode: "\u004E" + readonly property string deleteColumn: "\u004F" + readonly property string deleteMaterial: "\u0050" + readonly property string deleteRow: "\u0051" + readonly property string deleteTable: "\u0052" + readonly property string detach: "\u0053" + readonly property string distributeBottom: "\u0054" + readonly property string distributeCenterHorizontal: "\u0055" + readonly property string distributeCenterVertical: "\u0056" + readonly property string distributeLeft: "\u0057" + readonly property string distributeOriginBottomRight: "\u0058" + readonly property string distributeOriginCenter: "\u0059" + readonly property string distributeOriginNone: "\u005A" + readonly property string distributeOriginTopLeft: "\u005B" + readonly property string distributeRight: "\u005C" + readonly property string distributeSpacingHorizontal: "\u005D" + readonly property string distributeSpacingVertical: "\u005E" + readonly property string distributeTop: "\u005F" + readonly property string download: "\u0060" + readonly property string downloadUnavailable: "\u0061" + readonly property string downloadUpdate: "\u0062" + readonly property string downloaded: "\u0063" + readonly property string edit: "\u0064" + readonly property string eyeDropper: "\u0065" + readonly property string favorite: "\u0066" + readonly property string flowAction: "\u0067" + readonly property string flowTransition: "\u0068" + readonly property string fontStyleBold: "\u0069" + readonly property string fontStyleItalic: "\u006A" + readonly property string fontStyleStrikethrough: "\u006B" + readonly property string fontStyleUnderline: "\u006C" + readonly property string gradient: "\u006D" + readonly property string gridView: "\u006E" + readonly property string idAliasOff: "\u006F" + readonly property string idAliasOn: "\u0070" + readonly property string infinity: "\u0071" + readonly property string keyframe: "\u0072" + readonly property string linkTriangle: "\u0073" + readonly property string linked: "\u0074" + readonly property string listView: "\u0075" + readonly property string lockOff: "\u0076" + readonly property string lockOn: "\u0077" + readonly property string mergeCells: "\u0078" + readonly property string minus: "\u0079" + readonly property string mirror: "\u007A" + readonly property string newMaterial: "\u007B" + readonly property string openMaterialBrowser: "\u007C" + readonly property string orientation: "\u007D" + readonly property string paddingEdge: "\u007E" + readonly property string paddingFrame: "\u007F" + readonly property string pasteStyle: "\u0080" + readonly property string pause: "\u0081" + readonly property string pin: "\u0082" + readonly property string play: "\u0083" + readonly property string plus: "\u0084" + readonly property string promote: "\u0085" + readonly property string readOnly: "\u0086" + readonly property string redo: "\u0087" + readonly property string rotationFill: "\u0088" + readonly property string rotationOutline: "\u0089" + readonly property string search: "\u008A" + readonly property string sectionToggle: "\u008B" + readonly property string splitColumns: "\u008C" + readonly property string splitRows: "\u008D" + readonly property string startNode: "\u008E" + readonly property string testIcon: "\u008F" + readonly property string textAlignBottom: "\u0090" + readonly property string textAlignCenter: "\u0091" + readonly property string textAlignJustified: "\u0092" + readonly property string textAlignLeft: "\u0093" + readonly property string textAlignMiddle: "\u0094" + readonly property string textAlignRight: "\u0095" + readonly property string textAlignTop: "\u0096" + readonly property string textBulletList: "\u0097" + readonly property string textFullJustification: "\u0098" + readonly property string textNumberedList: "\u0099" + readonly property string tickIcon: "\u009A" + readonly property string translationCreateFiles: "\u009B" + readonly property string translationCreateReport: "\u009D" + readonly property string translationExport: "\u009E" + readonly property string translationImport: "\u009F" + readonly property string translationSelectLanguages: "\u00A0" + readonly property string translationTest: "\u00A1" + readonly property string transparent: "\u00A2" + readonly property string triState: "\u00A3" + readonly property string triangleArcA: "\u00A4" + readonly property string triangleArcB: "\u00A5" + readonly property string triangleCornerA: "\u00A6" + readonly property string triangleCornerB: "\u00A7" + readonly property string unLinked: "\u00A8" + readonly property string undo: "\u00A9" + readonly property string unpin: "\u00AA" + readonly property string upDownIcon: "\u00AB" + readonly property string upDownSquare2: "\u00AC" + readonly property string visibilityOff: "\u00AE" + readonly property string visibilityOn: "\u00AF" + readonly property string wildcard: "\u00B0" + readonly property string wizardsAutomotive: "\u00B1" + readonly property string wizardsDesktop: "\u00B2" + readonly property string wizardsGeneric: "\u00B3" + readonly property string wizardsMcuEmpty: "\u00B4" + readonly property string wizardsMcuGraph: "\u00B5" + readonly property string wizardsMobile: "\u00B6" + readonly property string wizardsUnknown: "\u00B7" + readonly property string zoomAll: "\u00B8" + readonly property string zoomIn: "\u00B9" + readonly property string zoomOut: "\u00BA" + readonly property string zoomSelection: "\u00BB" readonly property font iconFont: Qt.font({ "family": controlIcons.name, diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml index f63aa36ab14..75d21a8e29b 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml @@ -32,13 +32,21 @@ QtObject { property real baseHeight: 29 property real baseFont: 12 + property real mediumFont: 14 + property real bigFont: 16 property real baseIconFont: 12 + property real bigIconFont: 26 property real scaleFactor: 1.0 property real height: Math.round(values.baseHeight * values.scaleFactor) - property real myFontSize: Math.round(values.baseFont * values.scaleFactor) - property real myIconFontSize: Math.round(values.baseIconFont * values.scaleFactor) + property real baseFontSize: Math.round(values.baseFont * values.scaleFactor) + property real myFontSize: values.baseFontSize // TODO: rename all refs to myFontSize -> baseFontSize then remove myFontSize + property real mediumFontSize: Math.round(values.mediumFont * values.scaleFactor) + property real bigFontSize: Math.round(values.bigFont * values.scaleFactor) + property real baseIconFontSize: Math.round(values.baseIconFont * values.scaleFactor) + property real myIconFontSize: values.baseIconFontSize; // TODO: rename all refs to myIconFontSize -> baseIconFontSize then remove myIconFontSize + property real bigIconFontSize: Math.round(values.bigIconFont * values.scaleFactor) property real squareComponentWidth: values.height property real smallRectWidth: values.height / 2 * 1.5 diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf index 25c4b1e44320ed71d97ff0322d562e4f67ce615f..5a3e99d4590b33cddc9812e0435c01e03ca491c6 100644 GIT binary patch delta 5715 zcmaE`mhr__#(D-u1_lORh6V;^h5$FW5Z~ETRahAqqRucdFv$1^>l;P3rd(oRV2og3 zU`R;LO)R+Y$1|0If$0YW17lEfS&0IJB2y&;1B(v>0|QHXVsXL${|w9w3{giI7#KLx zb1KthxZMsiFtGM8Ft}~VNKH&pzEma5z`$^afq}s+BO|pwk?ZsSi3|*kA`A=+DjB&Y z6{6KVxeN@9I~W)k9dh!M6K}Vj;$vW731DDgw8~AaC}8-&aGHUE(Sm`2K_M?OH&v){ zPap$>j|~F@)3t*9;u4cRY9|;N{6!cTxYvO_$H>6&II*)Qp5Nvx12^*v1_lO(drPF# zAhe5g+W!y#&Dg7%bwDm=U;+s-;xkh+=;6|HGd=_G*v_R1QM1fJNpo%wiCl z?8Kq zFSr@F#kg&_^SI}6AK|{i{e?$>M}?<~=MXO+uOIKUdfq2|418*Q6?`lB_VIJ@oAFQL zKOw*&;2|(eV3WWJK|aAe!A*icgdBt-go=dL2}=n_38x8H39k~~C&DHoBH|=cBJxMn zL^MUTPxO%J1<`+EI$~C05n}7aeu=w?PY{15VIU)SLW{U4=u;no(3l%*brA zEQbP{j{IpYd+o^{9J1{4I;Z7zbmTV|aFjEu2{DKpvF8Kl=ZY7#RQm`G1=E z4O0_?AOnLcAEUa69ued=u7j696ZyLY!p z|80?%W2%*AV3^#%wT(%Tb+ZNcLPi^Y1|bGf1_=gf1_nk&K}C>5Ks2MWsWB_NAgj5c zxVj>`i>;xv4 z37h?StC;GAK-q)&CbI$q8v_r65Q7AR9D_Q8A%i)CErTT}|Cwj1jC^xE`!e6(l0g3RVPBDh!rZ2a}8uaqKbdvF$M&I_i3*{Mu^b zIu07DA_^uCjARA0bj{UtS=B_?#8{bxnHf2>IGEST$tsFz2MLI2@=5>cVdB!_VrIUg z#U-jC@h485Pg+HSnUhn8gNNBuLP54(M)A*15jkaPVQ*PoF)gWRVO33Tm1DA+;*$D| z42&;XUa)j9E!XDI@zxZSP?h?#jz?HsP>^{6C$FXe`yU@BR$Ue*CJkjlO(PB+bpiQ* zS)$4^GRkwLj09A4->B1nUvR?-qS{?{R-pdqQm*dlGgW~5%DAgdsy>8PS6<*lT= zP+Ce=QsU18O(8BV_F6er0Tnd{1||ma|JBSfOg9;n89+s&k(r5_vZ4~3sGx|Lv7(Wg zEu)E=5}Pa|C~YdKnF^Yi8M99Q&aW%Y#mC3>cXvoxSqS6C;4)qjz9@%({tnU64ovow ztp$_?SMmw-l?DGZ1F857Qam|RK%I$=ee)y%OBPlK1}}!N$rnV#6_`ZT4eVIWh0K-t z7?qXOOij#;jl{$i?HG-X#6(5d)FyL@8Ys%>3)`}C@CXP=^5%3TatE5)g@@aj268io zas`^%hKET_P8HRt_uUZ7!Y!d@V(;Ro!l-R)>=b5a7v^Ma>=bTm8~(~zS=rcFS-H|k zMa2ljW?*1oXPC<1$Q;ZR$H2_M&LGdA$zaT2%izhtz$j{LYAkAO%4#MiF2^J;X0EQJ z$D*#rF2g7$BBZ1ScB7~W1DquWa(KNN$i?DHYUWURHb$ALWUmY3K0cg{{nM_x|%p}n?_tp@n*~<`QpzL=R}^C#GkLz)6-i|VV+Lmi23AFA z@_`m~N^FyR#hsWOq9}Ck+y6}FnM@ZM*ciAOgcu|l z7#P+0nAr{a7}eB;<(S3I8QJBS>=>E*b!xk=?~LN&5@a{4?YyxwQuoheT{d+rKOPhetWWuC;u%E{8o$hf&#dIMwqZEzORXJB9?CX1Mr@<38XMO@(T5irGsk!3!E@)P4Jhp1#pb{B!aN2HtuHqVvSW2_frkY~_muw-Ch zlw%YX0hNRxKiD!V3o43&@{OX2nKh%SqM)(39izFaiJCSeI1h^%vly9~f-#u%GJABEnfZs|?US8~ynEd5fN2iz=r_JT^dW@3NkmxosGX_UCqlmGQ znW><$pvdF{3X-z@|6cS)ofKCT7grQ#+`cX-hRNjb`S!}nb|ymxhRr_|v>8=iGV3tN zGN>>xuqdgio69j88=0BgF{-KSF)FdKtDC8?h|4if_EM5cvz@nQo~?Eq!+(bVaq-U1 z@oxXV{`<(tf5^*C$JWWoHu&ECU|UB=TV0S`{Qv*)Zj6WRYUj+U1+|En7})>YF%xCg zIVGuR467de`}*$_BOft#f!fBF%rBT^!Eq*T&uC^W2P#sP^qEZ6^%y6+C>t^>rI=5y zR5oJLF`T?kS$6YTh>SwMtw@>W$PCQ<&$w^Wq{85o!urvHy+ z4rBVwz`!8Spf{OEO+iP@Sd|@7bgQee&19OvFDkqLVPT;GlGL!U1S`_8U_AFnLXLUOpODQB>gSmm)g}jMi!-uL&d^q17m`wyQ{-Wu zJW*R(!NAWzMSzQ`QgJ&2Bg5qX{mem3#~5T77*vf#z;)u}^V-tP?ybg?-)l>VSQs1C zxpeOns9n0Ul)pPA)kjTh+GGWt_!vP3Sx{P$V+3nbXH;iYGc~be6f&}7G|^*J;$svM zV~WVwn3ldFqdVu{O&vz{nDh;T>n4YVO$HInLg^bWY)Fs(cT<~DBfB?aL;7FGu*vJD zgoaL8H+k}X9gTWW9m&nW!yv*S!N6b+t|1j6wIrjWETf>JpecxOlQ3Xh%3xXNG{@*tVBgV^;;s;R-pL}08WAc6jRz}vzyhdr0x9Z7B ztD7^=5aRvwlvh}omsy%u2vi2JFsS`^U_LZC#n@r8p1uf^Zr@~AeN|ycJthr3M?F19 zy?<{YG{fXN{l$|rjjbWd{!A`4z^-ha!ES^y#mRdOaVZnq%wzPQSy&L<4mM^mXRv0l zXJAlO=VP2KXJ*G_6FNEG%#fKm!glf$GtA7nsaQNJ4f7$w>KMoQk0l1Gql8%lv|=l7XK=o!0`V*^9!bGunq>+$#)Ip`I&S7 z@)>!07&Cn`^7Qy~d-6snQ8g1!reZ^tfAPwOMoNqem5mtK85sT}?9ykjW^iTjojk!& znvrcXx3e0f*<@p9HIQvF&azBge3NUOl|Ys*be3`A<>KVy6XN3*74_S^8C0sUF^K%P zV7|hvz`)NS!=S-n#$eCj4l10**aeX~Qo=@N=8(48WJVWrUL^wqrG7q9MiIVpK9R}p zE=GJtO3V(*hJR)Y^Y97_OHS@`F;G z^5+!b=i0`_&(9UY$H~b)SL*|SP>64$jfqI*$S~an2 z)pb_~YmIQ1w??go7&+}KD(uS38Bonp#BR=3cX>DD8qSZ=0&gXE+RhGCCGq`tWWLVK z!@$bG%^=7i&Y;0yGFi?;(u&Cl)t6={HS2A^o}+d>J$6TX{9a$!kd9o*9-Lg_p~;uA z;qMAiRSK$Zr!Y)j)6l2d#OiD~C%ACB`^AM{sgD!*V+lPuq~@fSq{75DZ}Vql zR?15)hY9Evm!*s67o_IF1e}WU%ZpPtZw%sNQD$IZ{Qn<3J_>3TGu-oe3mSq&8cLN; F0{{Ufa?1b! delta 4973 zcmaE{mGQw^#(D-u1_lORh6V;^h5$FW5Z`Hc?yX~B2s^^Sz#!uvtZx+Aks8Fnz!<^6 zz>tufn^Gh^)1_p*Z3=9lr85ybdiCka*Ph?Z^mBDtORl~0~1J?fdQnES?RwSLlpCi|9}4Eu~&mcpmGq31uQb1VIc$e zWG6;tUXYJ_n7WwuFfcGam|VoDDY}hW;lBk#6tfb;DTs*-49p6XH!ub$Fo-koG4L_4 zFff1vo`FI1KNv9ZF~~C{G3ZTJV~U>K%2YP_CzBrIw#gdI_xQnB-VYb=UBh7$+3m7m9h1)9b@~&F2Ekd zzJx=ABaUMV$0?2Hn#Hw_n~mF#dlvUC9ub}- zo;sc-ybQbsyl%V`cn|Q&@CETr;k(AK#qY!)!9S1xpMaA$fM|?IBRkb>q18F$$D%)j7*cO*}R##G%Y5dU<+VYG?1Ju!~T@fcrp`* z3b!1iHKVAAn6Z%^^JF~^1!ECqk<(i8yy8N9?938=E!$3PYw?3n|HO6V<#kRgXzR$I z){z%g5;-le!@(yc&a=6Kqny!6m_eLDnt?&pNRCldP*G6PNKAxHSy50?(8NqwP*IRo zQBaZTGaEPCpP!7CoP2zo|F$u$v4%YFfjiA^ZzvS8>S`( zUIqq6bw)NNbw++hMlmCEM&=vB|C~9+IsQ2di!kmJ^%P^=$5g`iw}pqBsg{rTZ;QM< zQ_bWKu5C<0ESnv;7c#nvGe|MWGAJ-8GcYhJ3Mzu!!DwnM%C2Z^Drl;#s4OZds>&|N zYAz_QuE;E|$lUek3bRh!9|^|EhaWLA$;b zbtZPU&3ZzXEcRXuAq)u&42()@>dIoma-4?jCVGtOdW=eJ?0hVu>IQbK=0fJmd`!wp zdW@zfX2wQxjN*!REXGD+q9S}uW|J3-8E6YBhzhXVvU2bU2ubqhbR@EC+9_+eYJ}S< z+i9{hhO!7tD`|+@hKs9Puuc9VreS569pbLW$St8}V(;Ro!l-SlWGbibqG=ZD6Y;Z&&bHE zhCLEX)c+n)S7)+eLW;-Fp!j7xlf9g{VqsiL5RgWK(Rk7 z7oPy<4RLt|F-FF=_&66`Z*9hCA>9daxw9*xV;oJH_^)btXe(H2OJ@E%e95nX--<(! zmy1z+5LFF8;T0Z(?$!ii-Qc7rjv@#TCWH6~!61uM3J{GWmPnB~jj{vYp9r^ABZh z#-JO_(hQ0W8Vn39N^0una*W1CX6AN`YU+AS%1UhP>UNAqV&Za)AZr9g6-9L!MVY)0 zZgbbRofj59*EZPNI#@F)*D`FLt+w0F-8r1o!>lik9-IUPK zDeERPFihq*)Tn1=;AIeI5MhvEP+(v%7d5tJR2CQ2WVB_}WMl>NSQ!;%83h#uO+kd4 zgoFoU8e{#x#(%Sz{zyptWBWIsx$93EbM&7p=fuS&B%s)wiDfn8-+rb-#=m=+ME<@3 z6#{GwdjF>~?_%1_z`(%9zz4B2l7T^0(AZR)QPkX65JU+IgL$e*mNI@3M}U7pF#Fd3 z`z9gb#b`2r{=YMfZ^XrKpxQY3v0(hKE}y9W@aps+J8<@-e{zvCZ#RO zJVS{0&r@DuVP0lwULgh+2DSeV%!imI8MqlF8B`ez85kHRJDP~Hh>GwrPIfRcU=~$r zn(St*Dxx5!BFdzp=cuRWsQ2#;gq~byyqE)*whxnwOmJv>XDT-NfXPyvT16*sGsUiT z^E*>f7GWUXXB)3|W{oT{R|mS($D=XvM+k zB*mc2pv_>+V98+5;0kJkGYBv!FbJ^fF{+D+GaH+kD~hQrnwh98vZ;%R8>^X^E3+{w zv9qx&s;LW$u^Zbls!e9MF_7cp`nN~q8_zaQ&Mkb5YnT|5bQl%2lV1r4Y~f^M|ema>P3GNZXzeN@{&k(fHs%(jnW^-)a>43n4J zWHFmDcuW?tHRtDJWHhp4G&NB(1()uV!);CZ0=fbibpksB7&-U@y81z7==|3b z&>6%mz{2R?70ANR!Zi81?GjUdP!+1C#LuX1ZpR3!L*yBmTB}&a*{b5J1Z|YXwUv3Q z7?)h;=DuuxSzcK{PUJEuU#+nFET#c2H&{hQ_*j({O%;vom`zReSSN3^S7hfEk`fi? zWS)H8UXESePF_Ga&t3p;^t=F$jr_CCyIF)H}@ZZ z5SM{r@)-wXP89}o273l~hUm#0j&h8wlhqylIZd*|LadEN{~1lLb@XPJR5Q}m6T3b6 ztfQ5IDuXS9JA*$111F=Zk{+|VnyH$JxgD#qksPzQn4y>mJ0Gj69E*`1v$-CNvJxN5 zWHTqldNx^y;N)r>*S_Qw6#*u8b_oBUwupXsSaD8qc&MSUm`-G9v6)_6kej_K|ICpLf zja`IYgpZM(jh&BKSxH?HR2Zr&E2%5#F{-QCF`3)3n1d=jaXB_|Ii|@coufSX7-20- z=42sWW)>zEE>`w1W^O(qP8H#QliB2iRfKIBnVHzRn3>qQd04r1jC9nPUG&6fYjR77 zi;3{@GV-X%{B50_;^JX1rY>%%%=Aggh%uDgK%J49k%^6yiJ6Oomy1 z&!E7d!k__;E zD9$ioumPu;5SSzOxv4SPF;0H!rUrHkue&TWI~V(89d{+Ln*!WrTv)l;Ir;d6__#$y z{Wfm~70V+3Etsz`D=-K!$TMg&STZ<)oB2j!?1JEyy&faDX)kPKW^SUU%?N6JOjk)XJ`7_)=2;h)*UJiNl3e4IR!jXaE%q#5KH)ELYd z+(Fq*OjJ;WO`8$gTowW~>GT*Uw|b~sAR?d1pHqOJYa16oKUWAJCks0(8&~0>L>6{2 z#uj4_PouwlAd=b5Q28$(D99grh_kSonwLzRJd26Cw!BYJf!>SfFVs%|HHdl|BT>ek8iUMec6TD7oi zJ?W*M2+|6wq`@^cV@JJRZLJ;t3Y(GBuA;)Oyqtjn&4EhTZSnP%hd40bTRI%%K%~n5 zw4EKKO6L3T$b6ld2i*A(WDsZ2VgPy9)L2y6R9)0qRN0ovSX5cm94x?Wgc%BVy~Tx_ zGd85BZ^-D*`FB%?Q9TCav&pPJn*4sf>9&6zL2V6CgJjBN8y|0e0md`nu@_KUlVlQ{ zJk7_IUx9HI12Y373nv2`gAwE1$*+7gc|90X8S)tN7&0017}6&z_=>T6q~_&i=A}=z z^*zLDz@W=uG?~TElG#AlXtIM}v9JOILs)82ab|v=f`Ngdo`HdZ!em?DfXPDs%8b)D zoB8W8Zq5sM%s4qW= @@ -63,6 +64,24 @@ inline bool singleSelection(const SelectionContext &selectionState) return selectionState.singleNodeIsSelected(); } +inline bool isModel(const SelectionContext &selectionState) +{ + ModelNode node = selectionState.currentSingleSelectedNode(); + return node.isValid() && node.isSubclassOf("QtQuick3D.Model"); +} + +inline bool modelHasMaterial(const SelectionContext &selectionState) +{ + ModelNode node = selectionState.currentSingleSelectedNode(); + + if (!node.isValid()) + return false; + + BindingProperty prop = node.bindingProperty("materials"); + + return prop.exists() && (!prop.expression().isEmpty() || !prop.resolveToModelNodeList().empty()); +} + inline bool selectionEnabled(const SelectionContext &selectionState) { return selectionState.showSelectionTools(); diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index f3a93650dfa..f4f85475280 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -24,6 +24,7 @@ ****************************************************************************/ #include "modelnodeoperations.h" +#include "designmodewidget.h" #include "modelnodecontextmenu_helper.h" #include "addimagesdialog.h" #include "layoutingridlayout.h" @@ -789,6 +790,37 @@ void addNewSignalHandler(const SelectionContext &selectionState) addSignalHandlerOrGotoImplementation(selectionState, true); } +// Open a model's material in the material editor +void editMaterial(const SelectionContext &selectionContext) +{ + ModelNode modelNode = selectionContext.currentSingleSelectedNode(); + QTC_ASSERT(modelNode.isValid(), return); + + BindingProperty prop = modelNode.bindingProperty("materials"); + if (!prop.exists()) + return; + + AbstractView *view = selectionContext.view(); + + ModelNode material; + + if (view->hasId(prop.expression())) { + material = view->modelNodeForId(prop.expression()); + } else { + QList materials = prop.resolveToModelNodeList(); + + if (materials.size() > 0) + material = materials.first(); + } + + if (material.isValid()) { + QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialEditor"); + + // to MaterialEditor and MaterialBrowser... + view->emitCustomNotification("selected_material_changed", {material}); + } +} + void addItemToStackedContainer(const SelectionContext &selectionContext) { AbstractView *view = selectionContext.view(); diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h index 317d2d15e7e..9c887eb1dbe 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h @@ -67,6 +67,7 @@ void layoutColumnLayout(const SelectionContext &selectionState); void layoutGridLayout(const SelectionContext &selectionState); void goImplementation(const SelectionContext &selectionState); void addNewSignalHandler(const SelectionContext &selectionState); +void editMaterial(const SelectionContext &selectionContext); void addSignalHandlerOrGotoImplementation(const SelectionContext &selectionState, bool addAlwaysNewSlot); void removeLayout(const SelectionContext &selectionContext); void removePositioner(const SelectionContext &selectionContext); diff --git a/src/plugins/qmldesigner/components/componentcore/theme.h b/src/plugins/qmldesigner/components/componentcore/theme.h index 627eca23a5b..d8853cc7906 100644 --- a/src/plugins/qmldesigner/components/componentcore/theme.h +++ b/src/plugins/qmldesigner/components/componentcore/theme.h @@ -72,6 +72,7 @@ public: animatedProperty, annotationBubble, annotationDecal, + applyMaterialToSelected, assign, bevelAll, bevelCorner, @@ -86,8 +87,10 @@ public: cornersAll, curveDesigner, curveEditor, + customMaterialEditor, decisionNode, deleteColumn, + deleteMaterial, deleteRow, deleteTable, detach, @@ -130,6 +133,8 @@ public: mergeCells, minus, mirror, + newMaterial, + openMaterialBrowser, orientation, paddingEdge, paddingFrame, diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp index 422ceaab7bb..efb9e24c1ca 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp @@ -30,6 +30,11 @@ #include "nodehints.h" #include "qmlvisualnode.h" +#include +#include +#include +#include + #include #include @@ -181,13 +186,41 @@ void Edit3DCanvas::dragEnterEvent(QDragEnterEvent *e) void Edit3DCanvas::dropEvent(QDropEvent *e) { - Q_UNUSED(e) - auto modelNode = QmlVisualNode::createQml3DNode(m_parent->view(), m_itemLibraryEntry, m_activeScene).modelNode(); + QTC_ASSERT(modelNode.isValid(), return); - if (modelNode.isValid()) { - 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()); } } diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index ac4cc1947b8..6102ce475e5 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -67,16 +67,7 @@ void Edit3DView::createEdit3DWidget() void Edit3DView::checkImports() { - bool has3dImport = false; - const QList imports = model()->imports(); - for (const auto &import : imports) { - if (import.url() == "QtQuick3D") { - has3dImport = true; - break; - } - } - - edit3DWidget()->showCanvas(has3dImport); + edit3DWidget()->showCanvas(model()->hasImport("QtQuick3D")); } WidgetInfo Edit3DView::widgetInfo() diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp new file mode 100644 index 00000000000..012b540914c --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp @@ -0,0 +1,271 @@ +/**************************************************************************** +** +** 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 "materialbrowsermodel.h" +#include "variantproperty.h" +#include +#include + +namespace QmlDesigner { + +MaterialBrowserModel::MaterialBrowserModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +MaterialBrowserModel::~MaterialBrowserModel() +{ +} + +int MaterialBrowserModel::rowCount(const QModelIndex &) const +{ + return m_materialList.count(); +} + +QVariant MaterialBrowserModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_materialList.count()) { + qWarning() << Q_FUNC_INFO << "invalid index requested"; + return {}; + } + + if (roleNames().value(role) == "materialName") { + QVariant objName = m_materialList.at(index.row()).variantProperty("objectName").value(); + return objName.isValid() ? objName : ""; + } + + if (roleNames().value(role) == "materialInternalId") + return m_materialList.at(index.row()).internalId(); + + if (roleNames().value(role) == "materialVisible") + return isMaterialVisible(index.row()); + + if (!roleNames().contains(role)) + qWarning() << Q_FUNC_INFO << "invalid role requested"; + + return {}; +} + +bool MaterialBrowserModel::isMaterialVisible(int idx) const +{ + if (!isValidIndex(idx)) + return false; + + return m_searchText.isEmpty() || m_materialList.at(idx).variantProperty("objectName") + .value().toString().contains(m_searchText, Qt::CaseInsensitive); +} + +bool MaterialBrowserModel::isValidIndex(int idx) const +{ + return idx > -1 && idx < rowCount(); +} + +QHash MaterialBrowserModel::roleNames() const +{ + static const QHash roles { + {Qt::UserRole + 1, "materialName"}, + {Qt::UserRole + 2, "materialInternalId"}, + {Qt::UserRole + 3, "materialVisible"}, + }; + return roles; +} + +bool MaterialBrowserModel::hasQuick3DImport() const +{ + return m_hasQuick3DImport; +} + +void MaterialBrowserModel::setHasQuick3DImport(bool b) +{ + if (b == m_hasQuick3DImport) + return; + + m_hasQuick3DImport = b; + emit hasQuick3DImportChanged(); +} + +void MaterialBrowserModel::setSearchText(const QString &searchText) +{ + QString lowerSearchText = searchText.toLower(); + + if (m_searchText == lowerSearchText) + return; + + m_searchText = lowerSearchText; + + bool isEmpty = false; + + // if selected material goes invisible, select nearest material + if (!isMaterialVisible(m_selectedIndex)) { + int inc = 1; + int incCap = m_materialList.count(); + while (!isEmpty && inc < incCap) { + if (isMaterialVisible(m_selectedIndex - inc)) { + selectMaterial(m_selectedIndex - inc); + break; + } else if (isMaterialVisible(m_selectedIndex + inc)) { + selectMaterial(m_selectedIndex + inc); + break; + } + ++inc; + isEmpty = !isValidIndex(m_selectedIndex + inc) + && !isValidIndex(m_selectedIndex - inc); + } + } + + if (isEmpty != m_isEmpty) { + m_isEmpty = isEmpty; + emit isEmptyChanged(); + } + + resetModel(); +} + +void MaterialBrowserModel::setMaterials(const QList &materials, bool hasQuick3DImport) +{ + m_materialList = materials; + m_materialIndexHash.clear(); + for (int i = 0; i < materials.size(); ++i) + m_materialIndexHash.insert(materials.at(i).internalId(), i); + + bool isEmpty = materials.size() == 0; + if (isEmpty != m_isEmpty) { + m_isEmpty = isEmpty; + emit isEmptyChanged(); + } + + if (hasQuick3DImport != m_hasQuick3DImport) { + m_hasQuick3DImport = hasQuick3DImport; + emit hasQuick3DImportChanged(); + } + + updateSelectedMaterial(); + resetModel(); +} + +void MaterialBrowserModel::removeMaterial(const ModelNode &material) +{ + if (!m_materialIndexHash.contains(material.internalId())) + return; + + m_materialList.removeOne(material); + int idx = m_materialIndexHash.value(material.internalId()); + m_materialIndexHash.remove(material.internalId()); + + // update index hash + for (int i = idx; i < rowCount(); ++i) + m_materialIndexHash.insert(m_materialList.at(i).internalId(), i); + + resetModel(); + + if (m_materialList.isEmpty()) { + m_isEmpty = true; + emit isEmptyChanged(); + } +} + +void MaterialBrowserModel::updateSelectedMaterial() +{ + selectMaterial(m_selectedIndex, true); +} + +void MaterialBrowserModel::updateMaterialName(const ModelNode &material) +{ + int idx = materialIndex(material); + if (idx != -1) + emit dataChanged(index(idx, 0), index(idx, 0), {roleNames().key("materialName")}); +} + +int MaterialBrowserModel::materialIndex(const ModelNode &material) const +{ + if (m_materialIndexHash.contains(material.internalId())) + return m_materialIndexHash.value(material.internalId()); + + return -1; +} + +ModelNode MaterialBrowserModel::materialAt(int idx) const +{ + if (isValidIndex(idx)) + return m_materialList.at(idx); + + return {}; +} + +void MaterialBrowserModel::resetModel() +{ + beginResetModel(); + endResetModel(); +} + +void MaterialBrowserModel::selectMaterial(int idx, bool force) +{ + if (m_materialList.size() == 0) { + m_selectedIndex = -1; + emit selectedIndexChanged(m_selectedIndex); + return; + } + + idx = std::max(0, std::min(idx, rowCount() - 1)); + + if (idx != m_selectedIndex || force) { + m_selectedIndex = idx; + emit selectedIndexChanged(idx); + } +} + +void MaterialBrowserModel::deleteMaterial(qint32 internalId) +{ + int idx = m_materialIndexHash.value(internalId); + if (isValidIndex(idx)) + m_materialList[idx].destroy(); +} + +void MaterialBrowserModel::renameMaterial(int idx, const QString &newName) +{ + ModelNode mat = m_materialList.at(idx); + emit renameMaterialTriggered(mat, newName); +} + +void MaterialBrowserModel::addNewMaterial() +{ + emit addNewMaterialTriggered(); +} + +void MaterialBrowserModel::applyToSelected(qint64 internalId, bool add) +{ + int idx = m_materialIndexHash.value(internalId); + if (idx != -1) { + ModelNode mat = m_materialList.at(idx); + emit applyToSelectedTriggered(mat, add); + } +} + +void MaterialBrowserModel::openMaterialEditor() +{ + QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialEditor"); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h new file mode 100644 index 00000000000..7d8fa15121b --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** 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 + +namespace QmlDesigner { + +class MaterialBrowserModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) + Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) + Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) + +public: + MaterialBrowserModel(QObject *parent = nullptr); + ~MaterialBrowserModel() override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + + void setSearchText(const QString &searchText); + + bool hasQuick3DImport() const; + void setHasQuick3DImport(bool b); + + void setMaterials(const QList &materials, bool hasQuick3DImport); + void removeMaterial(const ModelNode &material); + void updateMaterialName(const ModelNode &material); + void updateSelectedMaterial(); + int materialIndex(const ModelNode &material) const; + ModelNode materialAt(int idx) const; + + void resetModel(); + + Q_INVOKABLE void selectMaterial(int idx, bool force = false); + Q_INVOKABLE void deleteMaterial(int idx); + Q_INVOKABLE void renameMaterial(int idx, const QString &newName); + Q_INVOKABLE void addNewMaterial(); + Q_INVOKABLE void applyToSelected(qint64 internalId, bool add = false); + Q_INVOKABLE void openMaterialEditor(); + +signals: + void isEmptyChanged(); + void hasQuick3DImportChanged(); + void selectedIndexChanged(int idx); + void renameMaterialTriggered(const QmlDesigner::ModelNode &material, const QString &newName); + void applyToSelectedTriggered(const QmlDesigner::ModelNode &material, bool add = false); + void addNewMaterialTriggered(); + +private: + bool isMaterialVisible(int idx) const; + bool isValidIndex(int idx) const; + + QString m_searchText; + QList m_materialList; + QHash m_materialIndexHash; // internalId -> index + + int m_selectedIndex = 0; + bool m_isEmpty = true; + bool m_hasQuick3DImport = false; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp new file mode 100644 index 00000000000..0946411c9b5 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -0,0 +1,266 @@ +/**************************************************************************** +** +** 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 "materialbrowserview.h" +#include "materialbrowserwidget.h" +#include "materialbrowsermodel.h" +#include "nodeabstractproperty.h" +#include "qmlobjectnode.h" +#include "variantproperty.h" +#include +#include +#include + +#include + +namespace QmlDesigner { + +MaterialBrowserView::MaterialBrowserView(QObject *parent) + : AbstractView(parent) + +{} + +MaterialBrowserView::~MaterialBrowserView() +{} + +bool MaterialBrowserView::hasWidget() const +{ + return true; +} + +WidgetInfo MaterialBrowserView::widgetInfo() +{ + if (m_widget.isNull()) { + m_widget = new MaterialBrowserWidget; + connect(m_widget->materialBrowserModel().data(), SIGNAL(selectedIndexChanged(int)), + this, SLOT(handleSelectedMaterialChanged(int))); + connect(m_widget->materialBrowserModel().data(), + SIGNAL(applyToSelectedTriggered(const QmlDesigner::ModelNode &, bool)), + this, SLOT(handleApplyToSelectedTriggered(const QmlDesigner::ModelNode &, bool))); + connect(m_widget->materialBrowserModel().data(), + SIGNAL(renameMaterialTriggered(const QmlDesigner::ModelNode &, const QString &)), + this, SLOT(handleRenameMaterial(const QmlDesigner::ModelNode &, const QString &))); + connect(m_widget->materialBrowserModel().data(), SIGNAL(addNewMaterialTriggered()), + this, SLOT(handleAddNewMaterial())); + } + + return createWidgetInfo(m_widget.data(), + new WidgetInfo::ToolBarWidgetDefaultFactory(m_widget.data()), + "MaterialBrowser", + WidgetInfo::LeftPane, + 0, + tr("Material Browser")); +} + +void MaterialBrowserView::modelAttached(Model *model) +{ + AbstractView::modelAttached(model); + + m_widget->clearSearchFilter(); + m_hasQuick3DImport = model->hasImport("QtQuick3D"); + QTimer::singleShot(0, this, &MaterialBrowserView::refreshModel); +} + +void MaterialBrowserView::refreshModel() +{ + ModelNode matLib = modelNodeForId(Constants::MATERIAL_LIB_ID); + QList materials; + + if (m_hasQuick3DImport && matLib.isValid()) { + const QList matLibNodes = matLib.directSubModelNodes(); + for (const ModelNode &node : matLibNodes) { + if (isMaterial(node)) + materials.append(node); + } + } + + m_widget->materialBrowserModel()->setMaterials(materials, m_hasQuick3DImport); + + for (const ModelNode &node : std::as_const(materials)) + model()->nodeInstanceView()->previewImageDataForGenericNode(node, {}); +} + +bool MaterialBrowserView::isMaterial(const ModelNode &node) const +{ + if (!node.isValid() || node.isComponent()) + return false; + + return node.isSubclassOf("QtQuick3D.Material"); +} + +void MaterialBrowserView::modelAboutToBeDetached(Model *model) +{ + AbstractView::modelAboutToBeDetached(model); +} + +void MaterialBrowserView::selectedNodesChanged(const QList &selectedNodeList, + const QList &lastSelectedNodeList) +{ + if (!m_autoSelectModelMaterial) + return; + + // if selected object is a model, select its material in the material browser and editor + ModelNode selectedModel; + + for (const ModelNode &node : selectedNodeList) { + if (node.isSubclassOf("QtQuick3D.Model")) { + selectedModel = node; + break; + } + } + + if (selectedNodeList.size() > 1 || !selectedModel.isValid()) + return; + + QmlObjectNode qmlObjNode(selectedModel); + QString matExp = qmlObjNode.expression("materials"); + if (matExp.isEmpty()) + return; + + QString matId = matExp.remove('[').remove(']').split(',', Qt::SkipEmptyParts).at(0); + ModelNode mat = modelNodeForId(matId); + if (!mat.isValid()) + return; + + int idx = m_widget->materialBrowserModel()->materialIndex(mat); + m_widget->materialBrowserModel()->selectMaterial(idx); +} + +void MaterialBrowserView::modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap) +{ + if (isMaterial(node)) + m_widget->updateMaterialPreview(node, pixmap); +} + +void MaterialBrowserView::variantPropertiesChanged(const QList &propertyList, + PropertyChangeFlags propertyChange) +{ + for (const VariantProperty &property : propertyList) { + ModelNode node(property.parentModelNode()); + + if (isMaterial(node) && property.name() == "objectName") + m_widget->materialBrowserModel()->updateMaterialName(node); + } +} + +void MaterialBrowserView::nodeReparented(const ModelNode &node, + const NodeAbstractProperty &newPropertyParent, + const NodeAbstractProperty &oldPropertyParent, + PropertyChangeFlags propertyChange) +{ + if (!isMaterial(node)) + return; + + ModelNode newParentNode = newPropertyParent.parentModelNode(); + ModelNode oldParentNode = oldPropertyParent.parentModelNode(); + bool matAdded = newParentNode.isValid() && newParentNode.id() == Constants::MATERIAL_LIB_ID; + bool matRemoved = oldParentNode.isValid() && oldParentNode.id() == Constants::MATERIAL_LIB_ID; + + if (matAdded || matRemoved) { + refreshModel(); + + int idx = m_widget->materialBrowserModel()->materialIndex(node); + m_widget->materialBrowserModel()->selectMaterial(idx); + } +} + +void MaterialBrowserView::nodeAboutToBeRemoved(const ModelNode &removedNode) +{ + // removing the material editor node + if (removedNode.isValid() && removedNode.id() == Constants::MATERIAL_LIB_ID) { + m_widget->materialBrowserModel()->setMaterials({}, m_hasQuick3DImport); + return; + } + + // not a material under the material editor + if (!isMaterial(removedNode) + || removedNode.parentProperty().parentModelNode().id() != Constants::MATERIAL_LIB_ID) { + return; + } + + m_widget->materialBrowserModel()->removeMaterial(removedNode); +} + +void MaterialBrowserView::nodeRemoved(const ModelNode &removedNode, + const NodeAbstractProperty &parentProperty, + PropertyChangeFlags propertyChange) +{ + if (parentProperty.parentModelNode().id() != Constants::MATERIAL_LIB_ID) + return; + + m_widget->materialBrowserModel()->updateSelectedMaterial(); +} + +void MaterialBrowserView::importsChanged(const QList &addedImports, const QList &removedImports) +{ + bool hasQuick3DImport = model()->hasImport("QtQuick3D"); + + if (hasQuick3DImport == m_hasQuick3DImport) + return; + + m_hasQuick3DImport = hasQuick3DImport; + refreshModel(); + +} + +void MaterialBrowserView::customNotification(const AbstractView *view, const QString &identifier, + const QList &nodeList, const QList &data) +{ + if (view == this) + return; + + if (identifier == "selected_material_changed") { + int idx = m_widget->materialBrowserModel()->materialIndex(nodeList.first()); + if (idx != -1) + m_widget->materialBrowserModel()->selectMaterial(idx); + } +} + +void MaterialBrowserView::handleSelectedMaterialChanged(int idx) +{ + ModelNode matNode = m_widget->materialBrowserModel()->materialAt(idx); + // to MaterialEditor... + emitCustomNotification("selected_material_changed", {matNode}, {}); +} + +void MaterialBrowserView::handleApplyToSelectedTriggered(const ModelNode &material, bool add) +{ + // to MaterialEditor... + emitCustomNotification("apply_to_selected_triggered", {material}, {add}); +} + +void MaterialBrowserView::handleRenameMaterial(const ModelNode &material, const QString &newName) +{ + // to MaterialEditor... + emitCustomNotification("rename_material", {material}, {newName}); +} + +void MaterialBrowserView::handleAddNewMaterial() +{ + // to MaterialEditor... + emitCustomNotification("add_new_material"); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h new file mode 100644 index 00000000000..28e4694cd65 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** 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 MaterialBrowserWidget; + +class MaterialBrowserView : public AbstractView +{ + Q_OBJECT + +public: + MaterialBrowserView(QObject *parent = nullptr); + ~MaterialBrowserView() override; + + bool hasWidget() const override; + WidgetInfo widgetInfo() override; + + // AbstractView + void modelAttached(Model *model) override; + void modelAboutToBeDetached(Model *model) override; + void selectedNodesChanged(const QList &selectedNodeList, + const QList &lastSelectedNodeList) override; + void modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap) override; + void variantPropertiesChanged(const QList &propertyList, PropertyChangeFlags propertyChange) override; + void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, + const NodeAbstractProperty &oldPropertyParent, + AbstractView::PropertyChangeFlags propertyChange) override; + void nodeAboutToBeRemoved(const ModelNode &removedNode) override; + void nodeRemoved(const ModelNode &removedNode, const NodeAbstractProperty &parentProperty, + PropertyChangeFlags propertyChange) override; + void importsChanged(const QList &addedImports, const QList &removedImports) override; + void customNotification(const AbstractView *view, const QString &identifier, + const QList &nodeList, const QList &data) override; + +private: + void refreshModel(); + bool isMaterial(const ModelNode &node) const; + + QPointer m_widget; + bool m_hasQuick3DImport = false; + bool m_autoSelectModelMaterial = false; // TODO: wire this to some action + +private slots: + void handleSelectedMaterialChanged(int idx); + void handleApplyToSelectedTriggered(const QmlDesigner::ModelNode &material, bool add = false); + void handleRenameMaterial(const QmlDesigner::ModelNode &material, const QString &newName); + void handleAddNewMaterial(); +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp new file mode 100644 index 00000000000..4ca84cdfb9d --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** 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 "materialbrowserwidget.h" +#include "materialbrowsermodel.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace QmlDesigner { + +static QString propertyEditorResourcesPath() +{ +#ifdef SHARE_QML_PATH + if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources"; +#endif + return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString(); +} + +class PreviewImageProvider : public QQuickImageProvider +{ + QHash m_pixmaps; + +public: + PreviewImageProvider() + : QQuickImageProvider(Pixmap) {} + + void setPixmap(const ModelNode &node, const QPixmap &pixmap) + { + m_pixmaps.insert(node.internalId(), pixmap); + } + + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override + { + Q_UNUSED(requestedSize) + + QPixmap pixmap{150, 150}; + + qint32 internalId = id.toInt(); + if (m_pixmaps.contains(internalId)) + pixmap = m_pixmaps.value(internalId); + + if (size) + *size = pixmap.size(); + + return pixmap; + } +}; + +bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::FocusOut) { + if (obj == m_quickWidget.data()) + QMetaObject::invokeMethod(m_quickWidget->rootObject(), "closeContextMenu"); + } + + return QObject::eventFilter(obj, event); +} + +MaterialBrowserWidget::MaterialBrowserWidget() + : m_materialBrowserModel(new MaterialBrowserModel(this)) + , m_quickWidget(new QQuickWidget(this)) + , m_previewImageProvider(new PreviewImageProvider()) +{ + setWindowTitle(tr("Material Browser", "Title of material browser widget")); + setMinimumWidth(120); + + m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); + m_quickWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); + m_quickWidget->setClearColor(Theme::getColor(Theme::Color::DSpanelBackground)); + + m_quickWidget->rootContext()->setContextProperties({ + {"rootView", QVariant::fromValue(this)}, + {"materialBrowserModel", QVariant::fromValue(m_materialBrowserModel.data())}, + }); + + m_quickWidget->engine()->addImageProvider("materialBrowser", m_previewImageProvider); + Theme::setupTheme(m_quickWidget->engine()); + m_quickWidget->installEventFilter(this); + + auto layout = new QVBoxLayout(this); + layout->setContentsMargins({}); + layout->setSpacing(0); + layout->addWidget(m_quickWidget.data()); + + updateSearch(); + + setStyleSheet(Theme::replaceCssColors( + QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css")))); + + m_qmlSourceUpdateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F8), this); + connect(m_qmlSourceUpdateShortcut, &QShortcut::activated, this, &MaterialBrowserWidget::reloadQmlSource); + + reloadQmlSource(); +} + +void MaterialBrowserWidget::updateMaterialPreview(const ModelNode &node, const QPixmap &pixmap) +{ + m_previewImageProvider->setPixmap(node, pixmap); + int idx = m_materialBrowserModel->materialIndex(node); + if (idx != -1) + QMetaObject::invokeMethod(m_quickWidget->rootObject(), "refreshPreview", Q_ARG(QVariant, idx)); +} + +QList MaterialBrowserWidget::createToolBarWidgets() +{ + return {}; +} + +void MaterialBrowserWidget::handleSearchfilterChanged(const QString &filterText) +{ + if (filterText != m_filterText) { + m_filterText = filterText; + updateSearch(); + } +} + +QString MaterialBrowserWidget::qmlSourcesPath() +{ +#ifdef SHARE_QML_PATH + if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/materialBrowserQmlSource"; +#endif + return Core::ICore::resourcePath("qmldesigner/materialBrowserQmlSource").toString(); +} + +void MaterialBrowserWidget::clearSearchFilter() +{ + QMetaObject::invokeMethod(m_quickWidget->rootObject(), "clearSearchFilter"); +} + +void MaterialBrowserWidget::reloadQmlSource() +{ + const QString materialBrowserQmlPath = qmlSourcesPath() + "/MaterialBrowser.qml"; + + QTC_ASSERT(QFileInfo::exists(materialBrowserQmlPath), return); + + m_quickWidget->engine()->clearComponentCache(); + m_quickWidget->setSource(QUrl::fromLocalFile(materialBrowserQmlPath)); +} + +void MaterialBrowserWidget::updateSearch() +{ + m_materialBrowserModel->setSearchText(m_filterText); + m_quickWidget->update(); +} + +QQuickWidget *MaterialBrowserWidget::quickWidget() const +{ + return m_quickWidget.data(); +} + +QPointer MaterialBrowserWidget::materialBrowserModel() const +{ + return m_materialBrowserModel; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h new file mode 100644 index 00000000000..f5f737007e7 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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 "itemlibraryinfo.h" +#include "import.h" +#include "modelnode.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE +class QStackedWidget; +class QShortcut; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class MaterialBrowserModel; +class PreviewImageProvider; + +class MaterialBrowserWidget : public QFrame +{ + Q_OBJECT + +public: + MaterialBrowserWidget(); + ~MaterialBrowserWidget() = default; + + QList createToolBarWidgets(); + + static QString qmlSourcesPath(); + void clearSearchFilter(); + + QPointer materialBrowserModel() const; + void updateMaterialPreview(const ModelNode &node, const QPixmap &pixmap); + + Q_INVOKABLE void handleSearchfilterChanged(const QString &filterText); + + QQuickWidget *quickWidget() const; + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + +private: + void reloadQmlSource(); + void updateSearch(); + + QPointer m_materialBrowserModel; + QScopedPointer m_quickWidget; + + QShortcut *m_qmlSourceUpdateShortcut = nullptr; + PreviewImageProvider *m_previewImageProvider = nullptr; + + QString m_filterText; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp new file mode 100644 index 00000000000..bf365bf270e --- /dev/null +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp @@ -0,0 +1,378 @@ +/**************************************************************************** +** +** 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 "materialeditorcontextobject.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace QmlDesigner { + +MaterialEditorContextObject::MaterialEditorContextObject(QObject *parent) + : QObject(parent) +{ + qmlRegisterUncreatableType("ToolBarAction", 1, 0, "ToolBarAction", "Enum type"); +} + +QString MaterialEditorContextObject::convertColorToString(const QVariant &color) +{ + QString colorString; + QColor theColor; + if (color.canConvert(QVariant::Color)) { + theColor = color.value(); + } else if (color.canConvert(QVariant::Vector3D)) { + auto vec = color.value(); + theColor = QColor::fromRgbF(vec.x(), vec.y(), vec.z()); + } + + colorString = theColor.name(); + + if (theColor.alpha() != 255) { + QString hexAlpha = QString("%1").arg(theColor.alpha(), 2, 16, QLatin1Char('0')); + colorString.remove(0, 1); + colorString.prepend(hexAlpha); + colorString.prepend(QStringLiteral("#")); + } + return colorString; +} + +// TODO: this method is used by the ColorEditor helper widget, check if at all needed? +QColor MaterialEditorContextObject::colorFromString(const QString &colorString) +{ + return colorString; +} + +void MaterialEditorContextObject::changeTypeName(const QString &typeName) +{ + QTC_ASSERT(m_model && m_model->rewriterView(), return); + QTC_ASSERT(m_selectedMaterial.isValid(), return); + + if (m_selectedMaterial.simplifiedTypeName() == typeName) + return; + + // Ideally we should not misuse the rewriterView + // If we add more code here we have to forward the material editor view + RewriterView *rewriterView = m_model->rewriterView(); + + rewriterView->executeInTransaction("MaterialEditorContextObject::changeTypeName", [&] { + NodeMetaInfo metaInfo = m_model->metaInfo(typeName.toLatin1()); + + QTC_ASSERT(metaInfo.isValid(), return); + + // Create a list of properties available for the new type + PropertyNameList propertiesAndSignals(metaInfo.propertyNames()); + // Add signals to the list + const PropertyNameList signalNames = metaInfo.signalNames(); + for (const PropertyName &signal : signalNames) { + if (signal.isEmpty()) + continue; + + PropertyName name = signal; + QChar firstChar = QChar(signal.at(0)).toUpper().toLatin1(); + name[0] = firstChar.toLatin1(); + name.prepend("on"); + propertiesAndSignals.append(name); + } + + // Add dynamic properties and respective change signals + const QList matProps = m_selectedMaterial.properties(); + for (const auto &property : matProps) { + if (!property.isDynamic()) + continue; + + // Add dynamic property + propertiesAndSignals.append(property.name()); + // Add its change signal + PropertyName name = property.name(); + QChar firstChar = QChar(property.name().at(0)).toUpper().toLatin1(); + name[0] = firstChar.toLatin1(); + name.prepend("on"); + name.append("Changed"); + propertiesAndSignals.append(name); + } + + // Compare current properties and signals with the ones available for change type + QList incompatibleProperties; + for (const auto &property : matProps) { + if (!propertiesAndSignals.contains(property.name())) + incompatibleProperties.append(property.name()); + } + + Utils::sort(incompatibleProperties); + + // Create a dialog showing incompatible properties and signals + if (!incompatibleProperties.empty()) { + QString detailedText = tr("Incompatible properties:
"); + + for (const auto &p : std::as_const(incompatibleProperties)) + detailedText.append("- " + QString::fromUtf8(p) + "
"); + + detailedText.chop(QString("
").size()); + + QMessageBox msgBox; + msgBox.setTextFormat(Qt::RichText); + msgBox.setIcon(QMessageBox::Question); + msgBox.setWindowTitle(tr("Change Type")); + msgBox.setText(tr("Changing the type from %1 to %2 can't be done without removing incompatible properties.

%3") + .arg(m_selectedMaterial.simplifiedTypeName(), typeName, detailedText)); + msgBox.setInformativeText(tr("Do you want to continue by removing incompatible properties?")); + msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Ok); + + if (msgBox.exec() == QMessageBox::Cancel) + return; + + for (const auto &p : std::as_const(incompatibleProperties)) + m_selectedMaterial.removeProperty(p); + } + + if (m_selectedMaterial.isRootNode()) + rewriterView->changeRootNodeType(metaInfo.typeName(), metaInfo.majorVersion(), metaInfo.minorVersion()); + else + m_selectedMaterial.changeType(metaInfo.typeName(), metaInfo.majorVersion(), metaInfo.minorVersion()); + }); +} + +void MaterialEditorContextObject::insertKeyframe(const QString &propertyName) +{ + QTC_ASSERT(m_model && m_model->rewriterView(), return); + QTC_ASSERT(m_selectedMaterial.isValid(), return); + + // Ideally we should not missuse the rewriterView + // If we add more code here we have to forward the material editor view + RewriterView *rewriterView = m_model->rewriterView(); + + QmlTimeline timeline = rewriterView->currentTimeline(); + + QTC_ASSERT(timeline.isValid(), return); + + rewriterView->executeInTransaction("MaterialEditorContextObject::insertKeyframe", [&] { + timeline.insertKeyframe(m_selectedMaterial, propertyName.toUtf8()); + }); +} + +int MaterialEditorContextObject::majorVersion() const +{ + return m_majorVersion; +} + +void MaterialEditorContextObject::setMajorVersion(int majorVersion) +{ + if (m_majorVersion == majorVersion) + return; + + m_majorVersion = majorVersion; + + emit majorVersionChanged(); +} + +bool MaterialEditorContextObject::hasActiveTimeline() const +{ + return m_hasActiveTimeline; +} + +void MaterialEditorContextObject::setHasActiveTimeline(bool b) +{ + if (b == m_hasActiveTimeline) + return; + + m_hasActiveTimeline = b; + emit hasActiveTimelineChanged(); +} + +bool MaterialEditorContextObject::hasQuick3DImport() const +{ + return m_hasQuick3DImport; +} + +void MaterialEditorContextObject::setHasQuick3DImport(bool b) +{ + if (b == m_hasQuick3DImport) + return; + + m_hasQuick3DImport = b; + emit hasQuick3DImportChanged(); +} + +void MaterialEditorContextObject::setSelectedMaterial(const ModelNode &matNode) +{ + m_selectedMaterial = matNode; +} + +void MaterialEditorContextObject::setSpecificsUrl(const QUrl &newSpecificsUrl) +{ + if (newSpecificsUrl == m_specificsUrl) + return; + + m_specificsUrl = newSpecificsUrl; + emit specificsUrlChanged(); +} + +void MaterialEditorContextObject::setStateName(const QString &newStateName) +{ + if (newStateName == m_stateName) + return; + + m_stateName = newStateName; + emit stateNameChanged(); +} + +void MaterialEditorContextObject::setAllStateNames(const QStringList &allStates) +{ + if (allStates == m_allStateNames) + return; + + m_allStateNames = allStates; + emit allStateNamesChanged(); +} + +void MaterialEditorContextObject::setIsBaseState(bool newIsBaseState) +{ + if (newIsBaseState == m_isBaseState) + return; + + m_isBaseState = newIsBaseState; + emit isBaseStateChanged(); +} + +void MaterialEditorContextObject::setSelectionChanged(bool newSelectionChanged) +{ + if (newSelectionChanged == m_selectionChanged) + return; + + m_selectionChanged = newSelectionChanged; + emit selectionChangedChanged(); +} + +void MaterialEditorContextObject::setBackendValues(QQmlPropertyMap *newBackendValues) +{ + if (newBackendValues == m_backendValues) + return; + + m_backendValues = newBackendValues; + emit backendValuesChanged(); +} + +void MaterialEditorContextObject::setModel(Model *model) +{ + m_model = model; +} + +void MaterialEditorContextObject::triggerSelectionChanged() +{ + setSelectionChanged(!m_selectionChanged); +} + +void MaterialEditorContextObject::setHasAliasExport(bool hasAliasExport) +{ + if (m_aliasExport == hasAliasExport) + return; + + m_aliasExport = hasAliasExport; + emit hasAliasExportChanged(); +} + +void MaterialEditorContextObject::hideCursor() +{ + if (QApplication::overrideCursor()) + return; + + QApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); + + if (QWidget *w = QApplication::activeWindow()) + m_lastPos = QCursor::pos(w->screen()); +} + +void MaterialEditorContextObject::restoreCursor() +{ + if (!QApplication::overrideCursor()) + return; + + QApplication::restoreOverrideCursor(); + + if (QWidget *w = QApplication::activeWindow()) + QCursor::setPos(w->screen(), m_lastPos); +} + +void MaterialEditorContextObject::holdCursorInPlace() +{ + if (!QApplication::overrideCursor()) + return; + + if (QWidget *w = QApplication::activeWindow()) + QCursor::setPos(w->screen(), m_lastPos); +} + +int MaterialEditorContextObject::devicePixelRatio() +{ + if (QWidget *w = QApplication::activeWindow()) + return w->devicePixelRatio(); + + return 1; +} + +QStringList MaterialEditorContextObject::allStatesForId(const QString &id) +{ + if (m_model && m_model->rewriterView()) { + const QmlObjectNode node = m_model->rewriterView()->modelNodeForId(id); + if (node.isValid()) + return node.allStateNames(); + } + + return {}; +} + +bool MaterialEditorContextObject::isBlocked(const QString &propName) const +{ + if (!m_selectedMaterial.isValid()) + return false; + + if (!m_model || !m_model->rewriterView()) + return false; + + if (QmlObjectNode(m_selectedMaterial).isBlocked(propName.toUtf8())) + return true; + + return false; +} + +} // QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h new file mode 100644 index 00000000000..c1b9a880462 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h @@ -0,0 +1,157 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include + +namespace QmlDesigner { + +class MaterialEditorContextObject : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QUrl specificsUrl READ specificsUrl WRITE setSpecificsUrl NOTIFY specificsUrlChanged) + + Q_PROPERTY(QString stateName READ stateName WRITE setStateName NOTIFY stateNameChanged) + Q_PROPERTY(QStringList allStateNames READ allStateNames WRITE setAllStateNames NOTIFY allStateNamesChanged) + + Q_PROPERTY(bool isBaseState READ isBaseState WRITE setIsBaseState NOTIFY isBaseStateChanged) + Q_PROPERTY(bool selectionChanged READ selectionChanged WRITE setSelectionChanged NOTIFY selectionChangedChanged) + + Q_PROPERTY(int majorVersion READ majorVersion WRITE setMajorVersion NOTIFY majorVersionChanged) + + Q_PROPERTY(bool hasAliasExport READ hasAliasExport NOTIFY hasAliasExportChanged) + Q_PROPERTY(bool hasActiveTimeline READ hasActiveTimeline NOTIFY hasActiveTimelineChanged) + Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) + + Q_PROPERTY(QQmlPropertyMap *backendValues READ backendValues WRITE setBackendValues NOTIFY backendValuesChanged) + +public: + MaterialEditorContextObject(QObject *parent = nullptr); + + QUrl specificsUrl() const { return m_specificsUrl; } + QString stateName() const { return m_stateName; } + QStringList allStateNames() const { return m_allStateNames; } + + bool isBaseState() const { return m_isBaseState; } + bool selectionChanged() const { return m_selectionChanged; } + + QQmlPropertyMap *backendValues() const { return m_backendValues; } + + Q_INVOKABLE QString convertColorToString(const QVariant &color); + Q_INVOKABLE QColor colorFromString(const QString &colorString); + + Q_INVOKABLE void changeTypeName(const QString &typeName); + Q_INVOKABLE void insertKeyframe(const QString &propertyName); + + Q_INVOKABLE void hideCursor(); + Q_INVOKABLE void restoreCursor(); + Q_INVOKABLE void holdCursorInPlace(); + + Q_INVOKABLE int devicePixelRatio(); + + Q_INVOKABLE QStringList allStatesForId(const QString &id); + + Q_INVOKABLE bool isBlocked(const QString &propName) const; + + enum ToolBarAction { + ApplyToSelected = 0, + ApplyToSelectedAdd, + AddNewMaterial, + DeleteCurrentMaterial, + OpenMaterialBrowser + }; + Q_ENUM(ToolBarAction) + + int majorVersion() const; + void setMajorVersion(int majorVersion); + + bool hasActiveTimeline() const; + void setHasActiveTimeline(bool b); + + bool hasQuick3DImport() const; + void setHasQuick3DImport(bool b); + + bool hasAliasExport() const { return m_aliasExport; } + + void setSelectedMaterial(const ModelNode &matNode); + + void setSpecificsUrl(const QUrl &newSpecificsUrl); + void setStateName(const QString &newStateName); + void setAllStateNames(const QStringList &allStates); + void setIsBaseState(bool newIsBaseState); + void setSelectionChanged(bool newSelectionChanged); + void setBackendValues(QQmlPropertyMap *newBackendValues); + void setModel(QmlDesigner::Model *model); + + void triggerSelectionChanged(); + void setHasAliasExport(bool hasAliasExport); + +signals: + void specificsUrlChanged(); + void stateNameChanged(); + void allStateNamesChanged(); + void isBaseStateChanged(); + void selectionChangedChanged(); + void backendValuesChanged(); + void majorVersionChanged(); + void hasAliasExportChanged(); + void hasActiveTimelineChanged(); + void hasQuick3DImportChanged(); + +private: + QUrl m_specificsUrl; + + QString m_stateName; + QStringList m_allStateNames; + + int m_majorVersion = 1; + + QQmlPropertyMap *m_backendValues = nullptr; + QQmlComponent *m_qmlComponent = nullptr; + Model *m_model = nullptr; + + QPoint m_lastPos; + + bool m_isBaseState = false; + bool m_selectionChanged = false; + bool m_aliasExport = false; + bool m_hasActiveTimeline = false; + bool m_hasQuick3DImport = false; + + ModelNode m_selectedMaterial; +}; + +} // QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp new file mode 100644 index 00000000000..828e5fab006 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp @@ -0,0 +1,346 @@ +/**************************************************************************** +** +** 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 "materialeditorqmlbackend.h" + +#include "propertyeditorvalue.h" +#include "materialeditortransaction.h" +#include "materialeditorcontextobject.h" +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static QObject *variantToQObject(const QVariant &value) +{ + if (value.userType() == QMetaType::QObjectStar || value.userType() > QMetaType::User) + return *(QObject **)value.constData(); + + return nullptr; +} + +namespace QmlDesigner { + +class MaterialEditorImageProvider : public QQuickImageProvider +{ + QPixmap m_previewPixmap; + +public: + MaterialEditorImageProvider() + : QQuickImageProvider(Pixmap) {} + + void setPixmap(const QPixmap &pixmap) + { + m_previewPixmap = pixmap; + } + + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override + { + Q_UNUSED(requestedSize) + + QPixmap pixmap{150, 150}; + + if (id == "preview") { + if (!m_previewPixmap.isNull()) + pixmap = m_previewPixmap; + } else { + QString path = Core::ICore::resourcePath("qmldesigner/materialEditorQmlSources/images/" + id).toString(); + pixmap = QPixmap{path}; + } + + if (size) + *size = pixmap.size(); + + return pixmap; + } +}; + +MaterialEditorQmlBackend::MaterialEditorQmlBackend(MaterialEditorView *materialEditor) + : m_view(new QQuickWidget) + , m_materialEditorTransaction(new MaterialEditorTransaction(materialEditor)) + , m_contextObject(new MaterialEditorContextObject()) + , m_materialEditorImageProvider(new MaterialEditorImageProvider()) +{ + m_view->setResizeMode(QQuickWidget::SizeRootObjectToView); + m_view->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); + m_view->engine()->addImageProvider("materialEditor", m_materialEditorImageProvider); + m_contextObject->setBackendValues(&m_backendValuesPropertyMap); + m_contextObject->setModel(materialEditor->model()); + context()->setContextObject(m_contextObject.data()); + + QObject::connect(&m_backendValuesPropertyMap, &DesignerPropertyMap::valueChanged, + materialEditor, &MaterialEditorView::changeValue); +} + +MaterialEditorQmlBackend::~MaterialEditorQmlBackend() +{ +} + +PropertyName MaterialEditorQmlBackend::auxNamePostFix(const PropertyName &propertyName) +{ + return propertyName + "__AUX"; +} + +QVariant MaterialEditorQmlBackend::properDefaultAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, + const PropertyName &propertyName) +{ + const ModelNode node = qmlObjectNode.modelNode(); + const PropertyName auxName = propertyName; + + if (node.hasAuxiliaryData(auxName)) + return node.auxiliaryData(auxName); + + return {}; +} + +void MaterialEditorQmlBackend::createPropertyEditorValue(const QmlObjectNode &qmlObjectNode, + const PropertyName &name, + const QVariant &value, + MaterialEditorView *materialEditor) +{ + PropertyName propertyName(name); + propertyName.replace('.', '_'); + auto valueObject = qobject_cast(variantToQObject(backendValuesPropertyMap().value(QString::fromUtf8(propertyName)))); + if (!valueObject) { + valueObject = new PropertyEditorValue(&backendValuesPropertyMap()); + QObject::connect(valueObject, &PropertyEditorValue::valueChanged, &backendValuesPropertyMap(), &DesignerPropertyMap::valueChanged); + QObject::connect(valueObject, &PropertyEditorValue::expressionChanged, materialEditor, &MaterialEditorView::changeExpression); + QObject::connect(valueObject, &PropertyEditorValue::exportPropertyAsAliasRequested, materialEditor, &MaterialEditorView::exportPropertyAsAlias); + QObject::connect(valueObject, &PropertyEditorValue::removeAliasExportRequested, materialEditor, &MaterialEditorView::removeAliasExport); + backendValuesPropertyMap().insert(QString::fromUtf8(propertyName), QVariant::fromValue(valueObject)); + } + valueObject->setName(name); + valueObject->setModelNode(qmlObjectNode); + + if (qmlObjectNode.propertyAffectedByCurrentState(name) && !(qmlObjectNode.modelNode().property(name).isBindingProperty())) + valueObject->setValue(qmlObjectNode.modelValue(name)); + else + valueObject->setValue(value); + + if (propertyName != "id" && qmlObjectNode.currentState().isBaseState() + && qmlObjectNode.modelNode().property(propertyName).isBindingProperty()) { + valueObject->setExpression(qmlObjectNode.modelNode().bindingProperty(propertyName).expression()); + } else { + if (qmlObjectNode.hasBindingProperty(name)) + valueObject->setExpression(qmlObjectNode.expression(name)); + else + valueObject->setExpression(qmlObjectNode.instanceValue(name).toString()); + } +} + +void MaterialEditorQmlBackend::setValue(const QmlObjectNode &, const PropertyName &name, const QVariant &value) +{ + // Vector*D values need to be split into their subcomponents + if (value.type() == QVariant::Vector2D) { + const char *suffix[2] = {"_x", "_y"}; + auto vecValue = value.value(); + for (int i = 0; i < 2; ++i) { + PropertyName subPropName(name.size() + 2, '\0'); + subPropName.replace(0, name.size(), name); + subPropName.replace(name.size(), 2, suffix[i]); + auto propertyValue = qobject_cast(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(subPropName)))); + if (propertyValue) + propertyValue->setValue(QVariant(vecValue[i])); + } + } else if (value.type() == QVariant::Vector3D) { + const char *suffix[3] = {"_x", "_y", "_z"}; + auto vecValue = value.value(); + for (int i = 0; i < 3; ++i) { + PropertyName subPropName(name.size() + 2, '\0'); + subPropName.replace(0, name.size(), name); + subPropName.replace(name.size(), 2, suffix[i]); + auto propertyValue = qobject_cast(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(subPropName)))); + if (propertyValue) + propertyValue->setValue(QVariant(vecValue[i])); + } + } else if (value.type() == QVariant::Vector4D) { + const char *suffix[4] = {"_x", "_y", "_z", "_w"}; + auto vecValue = value.value(); + for (int i = 0; i < 4; ++i) { + PropertyName subPropName(name.size() + 2, '\0'); + subPropName.replace(0, name.size(), name); + subPropName.replace(name.size(), 2, suffix[i]); + auto propertyValue = qobject_cast( + variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(subPropName)))); + if (propertyValue) + propertyValue->setValue(QVariant(vecValue[i])); + } + } else { + PropertyName propertyName = name; + propertyName.replace('.', '_'); + auto propertyValue = qobject_cast(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(propertyName)))); + if (propertyValue) + propertyValue->setValue(value); + } +} + +QQmlContext *MaterialEditorQmlBackend::context() const +{ + return m_view->rootContext(); +} + +MaterialEditorContextObject *MaterialEditorQmlBackend::contextObject() const +{ + return m_contextObject.data(); +} + +QQuickWidget *MaterialEditorQmlBackend::widget() const +{ + return m_view; +} + +void MaterialEditorQmlBackend::setSource(const QUrl &url) +{ + m_view->setSource(url); +} + +Internal::QmlAnchorBindingProxy &MaterialEditorQmlBackend::backendAnchorBinding() +{ + return m_backendAnchorBinding; +} + +void MaterialEditorQmlBackend::updateMaterialPreview(const QPixmap &pixmap) +{ + m_materialEditorImageProvider->setPixmap(pixmap); + QMetaObject::invokeMethod(m_view->rootObject(), "refreshPreview"); +} + +DesignerPropertyMap &MaterialEditorQmlBackend::backendValuesPropertyMap() +{ + return m_backendValuesPropertyMap; +} + +MaterialEditorTransaction *MaterialEditorQmlBackend::materialEditorTransaction() const +{ + return m_materialEditorTransaction.data(); +} + +PropertyEditorValue *MaterialEditorQmlBackend::propertyValueForName(const QString &propertyName) +{ + return qobject_cast(variantToQObject(backendValuesPropertyMap().value(propertyName))); +} + +void MaterialEditorQmlBackend::setup(const QmlObjectNode &selectedMaterialNode, const QString &stateName, + const QUrl &qmlSpecificsFile, MaterialEditorView *materialEditor) +{ + if (selectedMaterialNode.isValid()) { + m_contextObject->setModel(materialEditor->model()); + + const PropertyNameList propertyNames = selectedMaterialNode.modelNode().metaInfo().propertyNames(); + for (const PropertyName &propertyName : propertyNames) + createPropertyEditorValue(selectedMaterialNode, propertyName, selectedMaterialNode.instanceValue(propertyName), materialEditor); + + // model node + m_backendModelNode.setup(selectedMaterialNode.modelNode()); + context()->setContextProperty("modelNodeBackend", &m_backendModelNode); + context()->setContextProperty("hasMaterial", QVariant(true)); + + // className + auto valueObject = qobject_cast(variantToQObject( + m_backendValuesPropertyMap.value(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY))); + if (!valueObject) + valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap); + valueObject->setName(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY); + valueObject->setModelNode(selectedMaterialNode.modelNode()); + valueObject->setValue(m_backendModelNode.simplifiedTypeName()); + QObject::connect(valueObject, + &PropertyEditorValue::valueChanged, + &backendValuesPropertyMap(), + &DesignerPropertyMap::valueChanged); + m_backendValuesPropertyMap.insert(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY, + QVariant::fromValue(valueObject)); + + // anchors + m_backendAnchorBinding.setup(selectedMaterialNode.modelNode()); + context()->setContextProperties( + QVector{ + {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)}, + {{"transaction"}, QVariant::fromValue(m_materialEditorTransaction.data())} + } + ); + + contextObject()->setSpecificsUrl(qmlSpecificsFile); + contextObject()->setStateName(stateName); + + QStringList stateNames = selectedMaterialNode.allStateNames(); + stateNames.prepend("base state"); + contextObject()->setAllStateNames(stateNames); + contextObject()->setSelectedMaterial(selectedMaterialNode); + contextObject()->setIsBaseState(selectedMaterialNode.isInBaseState()); + contextObject()->setHasAliasExport(selectedMaterialNode.isAliasExported()); + contextObject()->setHasActiveTimeline(QmlTimeline::hasActiveTimeline(selectedMaterialNode.view())); + + contextObject()->setSelectionChanged(false); + + NodeMetaInfo metaInfo = selectedMaterialNode.modelNode().metaInfo(); + contextObject()->setMajorVersion(metaInfo.isValid() ? metaInfo.majorVersion() : -1); + } else { + context()->setContextProperty("hasMaterial", QVariant(false)); + } +} + +QString MaterialEditorQmlBackend::propertyEditorResourcesPath() +{ +#ifdef SHARE_QML_PATH + if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources"; +#endif + return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString(); +} + +void MaterialEditorQmlBackend::emitSelectionToBeChanged() +{ + m_backendModelNode.emitSelectionToBeChanged(); +} + +void MaterialEditorQmlBackend::emitSelectionChanged() +{ + m_backendModelNode.emitSelectionChanged(); +} + +void MaterialEditorQmlBackend::setValueforAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, const PropertyName &name) +{ + const PropertyName propertyName = auxNamePostFix(name); + setValue(qmlObjectNode, propertyName, qmlObjectNode.modelNode().auxiliaryData(name)); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h new file mode 100644 index 00000000000..3cfdbc9bd88 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** 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 "designerpropertymap.h" +#include "qmlanchorbindingproxy.h" +#include "qmlmodelnodeproxy.h" + +#include + +class PropertyEditorValue; + +QT_BEGIN_NAMESPACE +class QQuickWidget; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class MaterialEditorContextObject; +class MaterialEditorImageProvider; +class MaterialEditorTransaction; +class MaterialEditorView; + +class MaterialEditorQmlBackend +{ + Q_DISABLE_COPY(MaterialEditorQmlBackend) + +public: + MaterialEditorQmlBackend(MaterialEditorView *materialEditor); + ~MaterialEditorQmlBackend(); + + void setup(const QmlObjectNode &selectedMaterialNode, const QString &stateName, const QUrl &qmlSpecificsFile, + MaterialEditorView *materialEditor); + void setValue(const QmlObjectNode &fxObjectNode, const PropertyName &name, const QVariant &value); + + QQmlContext *context() const; + MaterialEditorContextObject *contextObject() const; + QQuickWidget *widget() const; + void setSource(const QUrl &url); + Internal::QmlAnchorBindingProxy &backendAnchorBinding(); + void updateMaterialPreview(const QPixmap &pixmap); + DesignerPropertyMap &backendValuesPropertyMap(); + MaterialEditorTransaction *materialEditorTransaction() const; + + PropertyEditorValue *propertyValueForName(const QString &propertyName); + + static QString propertyEditorResourcesPath(); + + void emitSelectionToBeChanged(); + void emitSelectionChanged(); + + void setValueforAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, const PropertyName &name); + +private: + void createPropertyEditorValue(const QmlObjectNode &qmlObjectNode, + const PropertyName &name, const QVariant &value, + MaterialEditorView *materialEditor); + PropertyName auxNamePostFix(const PropertyName &propertyName); + QVariant properDefaultAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, const PropertyName &propertyName); + + QQuickWidget *m_view = nullptr; + Internal::QmlAnchorBindingProxy m_backendAnchorBinding; + QmlModelNodeProxy m_backendModelNode; + DesignerPropertyMap m_backendValuesPropertyMap; + QScopedPointer m_materialEditorTransaction; + QScopedPointer m_contextObject; + MaterialEditorImageProvider *m_materialEditorImageProvider = nullptr; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditortransaction.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditortransaction.cpp new file mode 100644 index 00000000000..d036a49c378 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditortransaction.cpp @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** 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 "materialeditortransaction.h" + +#include + +#include + +namespace QmlDesigner { + +MaterialEditorTransaction::MaterialEditorTransaction(QmlDesigner::MaterialEditorView *materialEditor) + : QObject(materialEditor), + m_materialEditor(materialEditor) +{ +} + +void MaterialEditorTransaction::start() +{ + if (!m_materialEditor->model()) + return; + if (m_rewriterTransaction.isValid()) + m_rewriterTransaction.commit(); + m_rewriterTransaction = m_materialEditor->beginRewriterTransaction(QByteArrayLiteral("MaterialEditorTransaction::start")); + m_timerId = startTimer(10000); +} + +void MaterialEditorTransaction::end() +{ + if (m_rewriterTransaction.isValid() && m_materialEditor->model()) { + killTimer(m_timerId); + m_rewriterTransaction.commit(); + } +} + +bool MaterialEditorTransaction::active() const +{ + return m_rewriterTransaction.isValid(); +} + +void MaterialEditorTransaction::timerEvent(QTimerEvent *timerEvent) +{ + if (timerEvent->timerId() != m_timerId) + return; + killTimer(timerEvent->timerId()); + if (m_rewriterTransaction.isValid()) + m_rewriterTransaction.commit(); +} + +} // namespace QmlDesigner + diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditortransaction.h b/src/plugins/qmldesigner/components/materialeditor/materialeditortransaction.h new file mode 100644 index 00000000000..2a6bf0b3efd --- /dev/null +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditortransaction.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** 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 "materialeditorview.h" + +namespace QmlDesigner { + +class MaterialEditorTransaction : public QObject +{ + Q_OBJECT + +public: + MaterialEditorTransaction(QmlDesigner::MaterialEditorView *materialEditor); + + Q_INVOKABLE void start(); + Q_INVOKABLE void end(); + + Q_INVOKABLE bool active() const; + +protected: + void timerEvent(QTimerEvent *event) override; + +private: + QmlDesigner::MaterialEditorView *m_materialEditor = nullptr; + QmlDesigner::RewriterTransaction m_rewriterTransaction; + int m_timerId = -1; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp new file mode 100644 index 00000000000..b72b91117fb --- /dev/null +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -0,0 +1,817 @@ +/**************************************************************************** +** +** 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 "materialeditorview.h" + +#include "materialeditorqmlbackend.h" +#include "materialeditorcontextobject.h" +#include "propertyeditorvalue.h" +#include "materialeditortransaction.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace QmlDesigner { + +MaterialEditorView::MaterialEditorView(QWidget *parent) + : AbstractView(parent) + , m_stackedWidget(new QStackedWidget(parent)) +{ + m_updateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F7), m_stackedWidget); + connect(m_updateShortcut, &QShortcut::activated, this, &MaterialEditorView::reloadQml); + + m_stackedWidget->setStyleSheet(Theme::replaceCssColors( + QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css")))); + m_stackedWidget->setMinimumWidth(250); +} + +void MaterialEditorView::ensureMaterialLibraryNode() +{ + if (!m_hasQuick3DImport) + return; + + m_materialLibrary = modelNodeForId(Constants::MATERIAL_LIB_ID); + if (m_materialLibrary.isValid()) + return; + + const QList materials = rootModelNode().subModelNodesOfType("QtQuick3D.Material"); + if (materials.isEmpty()) + return; + + // create material library node + TypeName nodeType = rootModelNode().isSubclassOf("QtQuick3D.Node") ? "Quick3D.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); + + // 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); + } +} + +MaterialEditorView::~MaterialEditorView() +{ + qDeleteAll(m_qmlBackendHash); +} + +// from material editor to model +void MaterialEditorView::changeValue(const QString &name) +{ + PropertyName propertyName = name.toUtf8(); + + if (propertyName.isNull() || locked() || noValidSelection() || propertyName == "id" + || propertyName == Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY) { + return; + } + + if (propertyName == "objectName") { + renameMaterial(m_selectedMaterial, m_qmlBackEnd->propertyValueForName("objectName")->value().toString()); + return; + } + + PropertyName underscoreName(propertyName); + underscoreName.replace('.', '_'); + PropertyEditorValue *value = m_qmlBackEnd->propertyValueForName(QString::fromLatin1(underscoreName)); + + if (!value) + return; + + if (propertyName.endsWith("__AUX")) { + commitAuxValueToModel(propertyName, value->value()); + return; + } + + const NodeMetaInfo metaInfo = m_selectedMaterial.metaInfo(); + + QVariant castedValue; + + if (metaInfo.isValid() && metaInfo.hasProperty(propertyName)) { + castedValue = metaInfo.propertyCastedValue(propertyName, value->value()); + } else { + qWarning() << __FUNCTION__ << propertyName << "cannot be casted (metainfo)"; + return; + } + + if (value->value().isValid() && !castedValue.isValid()) { + qWarning() << __FUNCTION__ << propertyName << "not properly casted (metainfo)"; + return; + } + + bool propertyTypeUrl = false; + + if (metaInfo.isValid() && metaInfo.hasProperty(propertyName)) { + if (metaInfo.propertyTypeName(propertyName) == "QUrl" + || metaInfo.propertyTypeName(propertyName) == "url") { + // turn absolute local file paths into relative paths + propertyTypeUrl = true; + QString filePath = castedValue.toUrl().toString(); + QFileInfo fi(filePath); + if (fi.exists() && fi.isAbsolute()) { + QDir fileDir(QFileInfo(model()->fileUrl().toLocalFile()).absolutePath()); + castedValue = QUrl(fileDir.relativeFilePath(filePath)); + } + } + } + + if (name == "state" && castedValue.toString() == "base state") + castedValue = ""; + + if (castedValue.type() == QVariant::Color) { + QColor color = castedValue.value(); + QColor newColor = QColor(color.name()); + newColor.setAlpha(color.alpha()); + castedValue = QVariant(newColor); + } + + if (!value->value().isValid() || (propertyTypeUrl && value->value().toString().isEmpty())) { // reset + removePropertyFromModel(propertyName); + } else { + // QVector*D(0, 0, 0) detects as null variant though it is valid value + if (castedValue.isValid() + && (!castedValue.isNull() || castedValue.type() == QVariant::Vector2D + || castedValue.type() == QVariant::Vector3D + || castedValue.type() == QVariant::Vector4D)) { + commitVariantValueToModel(propertyName, castedValue); + } + } + + requestPreviewRender(); +} + +static bool isTrueFalseLiteral(const QString &expression) +{ + return (expression.compare("false", Qt::CaseInsensitive) == 0) + || (expression.compare("true", Qt::CaseInsensitive) == 0); +} + +void MaterialEditorView::changeExpression(const QString &propertyName) +{ + PropertyName name = propertyName.toUtf8(); + + if (name.isNull() || locked() || noValidSelection()) + return; + + executeInTransaction("MaterialEditorView::changeExpression", [this, name] { + PropertyName underscoreName(name); + underscoreName.replace('.', '_'); + + QmlObjectNode qmlObjectNode(m_selectedMaterial); + PropertyEditorValue *value = m_qmlBackEnd->propertyValueForName(QString::fromLatin1(underscoreName)); + + if (!value) { + qWarning() << __FUNCTION__ << "no value for " << underscoreName; + return; + } + + if (m_selectedMaterial.metaInfo().isValid() && m_selectedMaterial.metaInfo().hasProperty(name)) { + if (m_selectedMaterial.metaInfo().propertyTypeName(name) == "QColor") { + if (QColor(value->expression().remove('"')).isValid()) { + qmlObjectNode.setVariantProperty(name, QColor(value->expression().remove('"'))); + return; + } + } else if (m_selectedMaterial.metaInfo().propertyTypeName(name) == "bool") { + if (isTrueFalseLiteral(value->expression())) { + if (value->expression().compare("true", Qt::CaseInsensitive) == 0) + qmlObjectNode.setVariantProperty(name, true); + else + qmlObjectNode.setVariantProperty(name, false); + return; + } + } else if (m_selectedMaterial.metaInfo().propertyTypeName(name) == "int") { + bool ok; + int intValue = value->expression().toInt(&ok); + if (ok) { + qmlObjectNode.setVariantProperty(name, intValue); + return; + } + } else if (m_selectedMaterial.metaInfo().propertyTypeName(name) == "qreal") { + bool ok; + qreal realValue = value->expression().toDouble(&ok); + if (ok) { + qmlObjectNode.setVariantProperty(name, realValue); + return; + } + } else if (m_selectedMaterial.metaInfo().propertyTypeName(name) == "QVariant") { + bool ok; + qreal realValue = value->expression().toDouble(&ok); + if (ok) { + qmlObjectNode.setVariantProperty(name, realValue); + return; + } else if (isTrueFalseLiteral(value->expression())) { + if (value->expression().compare("true", Qt::CaseInsensitive) == 0) + qmlObjectNode.setVariantProperty(name, true); + else + qmlObjectNode.setVariantProperty(name, false); + return; + } + } + } + + if (value->expression().isEmpty()) { + value->resetValue(); + return; + } + + if (qmlObjectNode.expression(name) != value->expression() || !qmlObjectNode.propertyAffectedByCurrentState(name)) + qmlObjectNode.setBindingProperty(name, value->expression()); + + requestPreviewRender(); + }); // end of transaction +} + +void MaterialEditorView::exportPropertyAsAlias(const QString &name) +{ + if (name.isNull() || locked() || noValidSelection()) + return; + + executeInTransaction("MaterialEditorView::exportPopertyAsAlias", [this, name] { + const QString id = m_selectedMaterial.validId(); + QString upperCasePropertyName = name; + upperCasePropertyName.replace(0, 1, upperCasePropertyName.at(0).toUpper()); + QString aliasName = id + upperCasePropertyName; + aliasName.replace(".", ""); //remove all dots + + PropertyName propertyName = aliasName.toUtf8(); + if (rootModelNode().hasProperty(propertyName)) { + Core::AsynchronousMessageBox::warning(tr("Cannot Export Property as Alias"), + tr("Property %1 does already exist for root component.").arg(aliasName)); + return; + } + rootModelNode().bindingProperty(propertyName).setDynamicTypeNameAndExpression("alias", id + "." + name); + }); +} + +void MaterialEditorView::removeAliasExport(const QString &name) +{ + if (name.isNull() || locked() || noValidSelection()) + return; + + executeInTransaction("MaterialEditorView::removeAliasExport", [this, name] { + const QString id = m_selectedMaterial.validId(); + + const QList bindingProps = rootModelNode().bindingProperties(); + for (const BindingProperty &property : bindingProps) { + if (property.expression() == (id + "." + name)) { + rootModelNode().removeProperty(property.name()); + break; + } + } + }); +} + +bool MaterialEditorView::locked() const +{ + return m_locked; +} + +void MaterialEditorView::currentTimelineChanged(const ModelNode &) +{ + m_qmlBackEnd->contextObject()->setHasActiveTimeline(QmlTimeline::hasActiveTimeline(this)); +} + +void MaterialEditorView::delayedResetView() +{ + // TODO: it seems the delayed reset is not needed. Leaving it commented out for now just in case it + // turned out to be needed. Otherwise will be removed after a small testing period. +// if (m_timerId) +// killTimer(m_timerId); +// m_timerId = startTimer(50); + resetView(); +} + +void MaterialEditorView::timerEvent(QTimerEvent *timerEvent) +{ + if (m_timerId == timerEvent->timerId()) + resetView(); +} + +void MaterialEditorView::resetView() +{ + if (!model()) + return; + + m_locked = true; + + if (m_timerId) + killTimer(m_timerId); + + setupQmlBackend(); + + if (m_qmlBackEnd) + m_qmlBackEnd->emitSelectionChanged(); + + QTimer::singleShot(0, this, &MaterialEditorView::requestPreviewRender); + + m_locked = false; + + if (m_timerId) + m_timerId = 0; +} + +// static +QString MaterialEditorView::materialEditorResourcesPath() +{ +#ifdef SHARE_QML_PATH + if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/materialEditorQmlSources"; +#endif + return Core::ICore::resourcePath("qmldesigner/materialEditorQmlSources").toString(); +} + +void MaterialEditorView::applyMaterialToSelectedModels(const ModelNode &material, bool add) +{ + if (m_selectedModels.isEmpty()) + return; + + QTC_ASSERT(material.isValid(), return); + + auto expToList = [](const QString &exp) { + QString copy = exp; + copy = copy.remove("[").remove("]"); + + QStringList tmp = copy.split(',', Qt::SkipEmptyParts); + for (QString &str : tmp) + str = str.trimmed(); + + return tmp; + }; + + auto listToExp = [](QStringList &stringList) { + if (stringList.size() > 1) + return QString("[" + stringList.join(",") + "]"); + + if (stringList.size() == 1) + return stringList.first(); + + return QString(); + }; + + executeInTransaction("MaterialEditorView::applyMaterialToSelectedModels", [&] { + for (const ModelNode &node : std::as_const(m_selectedModels)) { + QmlObjectNode qmlObjNode(node); + if (add) { + QStringList matList = expToList(qmlObjNode.expression("materials")); + matList.append(material.id()); + QString updatedExp = listToExp(matList); + qmlObjNode.setBindingProperty("materials", updatedExp); + } else { + qmlObjNode.setBindingProperty("materials", material.id()); + } + } + }); +} + +void MaterialEditorView::handleToolBarAction(int action) +{ + QTC_ASSERT(m_hasQuick3DImport, return); + + switch (action) { + case MaterialEditorContextObject::ApplyToSelected: { + applyMaterialToSelectedModels(m_selectedMaterial); + break; + } + + case MaterialEditorContextObject::ApplyToSelectedAdd: { + applyMaterialToSelectedModels(m_selectedMaterial, true); + break; + } + + 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); + }); + break; + } + + case MaterialEditorContextObject::DeleteCurrentMaterial: { + if (m_selectedMaterial.isValid()) + m_selectedMaterial.destroy(); + break; + } + + case MaterialEditorContextObject::OpenMaterialBrowser: { + QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialBrowser"); + break; + } + } +} + +void MaterialEditorView::setupQmlBackend() +{ + QUrl qmlPaneUrl; + QUrl qmlSpecificsUrl; + + if (m_selectedMaterial.isValid() && m_hasQuick3DImport) { + qmlPaneUrl = QUrl::fromLocalFile(materialEditorResourcesPath() + "/MaterialEditorPane.qml"); + + NodeMetaInfo metaInfo = m_selectedMaterial.metaInfo(); + QDir importDir(metaInfo.importDirectoryPath() + Constants::QML_DESIGNER_SUBFOLDER); + QString typeName = QString::fromUtf8(metaInfo.typeName().split('.').constLast()); + qmlSpecificsUrl = QUrl::fromLocalFile(importDir.absoluteFilePath(typeName + "Specifics.qml")); + } else { + qmlPaneUrl = QUrl::fromLocalFile(materialEditorResourcesPath() + "/EmptyMaterialEditorPane.qml"); + } + + MaterialEditorQmlBackend *currentQmlBackend = m_qmlBackendHash.value(qmlPaneUrl.toString()); + + QString currentStateName = currentState().isBaseState() ? currentState().name() : "invalid state"; + + if (!currentQmlBackend) { + currentQmlBackend = new MaterialEditorQmlBackend(this); + + m_stackedWidget->addWidget(currentQmlBackend->widget()); + m_qmlBackendHash.insert(qmlPaneUrl.toString(), currentQmlBackend); + + currentQmlBackend->setup(m_selectedMaterial, currentStateName, qmlSpecificsUrl, this); + + currentQmlBackend->setSource(qmlPaneUrl); + + QObject::connect(currentQmlBackend->widget()->rootObject(), SIGNAL(toolBarAction(int)), + this, SLOT(handleToolBarAction(int))); + } else { + currentQmlBackend->setup(m_selectedMaterial, currentStateName, qmlSpecificsUrl, this); + } + + currentQmlBackend->contextObject()->setHasQuick3DImport(m_hasQuick3DImport); + + m_stackedWidget->setCurrentWidget(currentQmlBackend->widget()); + + m_qmlBackEnd = currentQmlBackend; +} + +void MaterialEditorView::commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value) +{ + m_locked = true; + executeInTransaction("MaterialEditorView:commitVariantValueToModel", [&] { + QmlObjectNode(m_selectedMaterial).setVariantProperty(propertyName, value); + }); + m_locked = false; +} + +void MaterialEditorView::commitAuxValueToModel(const PropertyName &propertyName, const QVariant &value) +{ + m_locked = true; + + PropertyName name = propertyName; + name.chop(5); + + try { + if (value.isValid()) + m_selectedMaterial.setAuxiliaryData(name, value); + else + m_selectedMaterial.removeAuxiliaryData(name); + } + catch (const Exception &e) { + e.showException(); + } + m_locked = false; +} + +void MaterialEditorView::removePropertyFromModel(const PropertyName &propertyName) +{ + m_locked = true; + executeInTransaction("MaterialEditorView:removePropertyFromModel", [&] { + QmlObjectNode(m_selectedMaterial).removeProperty(propertyName); + }); + m_locked = false; +} + +bool MaterialEditorView::noValidSelection() const +{ + QTC_ASSERT(m_qmlBackEnd, return true); + return !QmlObjectNode::isValidQmlObjectNode(m_selectedMaterial); +} + +void MaterialEditorView::modelAttached(Model *model) +{ + AbstractView::modelAttached(model); + + m_locked = true; + + m_hasQuick3DImport = model->hasImport("QtQuick3D"); + + ensureMaterialLibraryNode(); + + if (!m_setupCompleted) { + reloadQml(); + m_setupCompleted = true; + } + resetView(); + + m_locked = false; +} + +void MaterialEditorView::modelAboutToBeDetached(Model *model) +{ + AbstractView::modelAboutToBeDetached(model); + m_qmlBackEnd->materialEditorTransaction()->end(); + + +} + +void MaterialEditorView::propertiesRemoved(const QList &propertyList) +{ + if (noValidSelection()) + return; + + for (const AbstractProperty &property : propertyList) { + ModelNode node(property.parentModelNode()); + + if (node.isRootNode()) + m_qmlBackEnd->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedMaterial).isAliasExported()); + + if (node == m_selectedMaterial || QmlObjectNode(m_selectedMaterial).propertyChangeForCurrentState() == node) { + setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).instanceValue(property.name())); + } + } +} + +void MaterialEditorView::variantPropertiesChanged(const QList &propertyList, PropertyChangeFlags /*propertyChange*/) +{ + if (noValidSelection()) + return; + + bool changed = false; + for (const VariantProperty &property : propertyList) { + ModelNode node(property.parentModelNode()); + + if (node == m_selectedMaterial || QmlObjectNode(m_selectedMaterial).propertyChangeForCurrentState() == node) { + if (m_selectedMaterial.property(property.name()).isBindingProperty()) + setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).instanceValue(property.name())); + else + setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).modelValue(property.name())); + + changed = true; + } + } + if (changed) + requestPreviewRender(); +} + +void MaterialEditorView::bindingPropertiesChanged(const QList &propertyList, PropertyChangeFlags /*propertyChange*/) +{ + if (noValidSelection()) + return; + + bool changed = false; + for (const BindingProperty &property : propertyList) { + ModelNode node(property.parentModelNode()); + + if (property.isAliasExport()) + m_qmlBackEnd->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedMaterial).isAliasExported()); + + if (node == m_selectedMaterial || QmlObjectNode(m_selectedMaterial).propertyChangeForCurrentState() == node) { + if (QmlObjectNode(m_selectedMaterial).modelNode().property(property.name()).isBindingProperty()) + setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).instanceValue(property.name())); + else + setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).modelValue(property.name())); + + changed = true; + } + } + if (changed) + requestPreviewRender(); +} + +void MaterialEditorView::auxiliaryDataChanged(const ModelNode &node, const PropertyName &name, const QVariant &) +{ + + if (noValidSelection() || !node.isSelected()) + return; + + m_qmlBackEnd->setValueforAuxiliaryProperties(m_selectedMaterial, name); +} + +// request render image for the selected material node +void MaterialEditorView::requestPreviewRender() +{ + if (m_selectedMaterial.isValid()) + model()->nodeInstanceView()->previewImageDataForGenericNode(m_selectedMaterial, {}); +} + +bool MaterialEditorView::hasWidget() const +{ + return true; +} + +WidgetInfo MaterialEditorView::widgetInfo() +{ + return createWidgetInfo(m_stackedWidget, nullptr, "MaterialEditor", WidgetInfo::RightPane, 0, tr("Material Editor")); +} + +void MaterialEditorView::selectedNodesChanged(const QList &selectedNodeList, + const QList &lastSelectedNodeList) +{ + m_selectedModels.clear(); + + for (const ModelNode &node : selectedNodeList) { + if (node.isSubclassOf("QtQuick3D.Model")) + m_selectedModels.append(node); + } +} + +void MaterialEditorView::currentStateChanged(const ModelNode &node) +{ + QmlModelState newQmlModelState(node); + Q_ASSERT(newQmlModelState.isValid()); + delayedResetView(); +} + +void MaterialEditorView::instancePropertyChanged(const QList > &propertyList) +{ + if (!m_selectedMaterial.isValid() || !m_qmlBackEnd) + return; + + m_locked = true; + + for (const QPair &propertyPair : propertyList) { + const ModelNode modelNode = propertyPair.first; + const QmlObjectNode qmlObjectNode(modelNode); + const PropertyName propertyName = propertyPair.second; + + if (qmlObjectNode.isValid() && modelNode == m_selectedMaterial && qmlObjectNode.currentState().isValid()) { + const AbstractProperty property = modelNode.property(propertyName); + if (!modelNode.hasProperty(propertyName) || modelNode.property(property.name()).isBindingProperty()) + setValue(modelNode, property.name(), qmlObjectNode.instanceValue(property.name())); + else + setValue(modelNode, property.name(), qmlObjectNode.modelValue(property.name())); + } + } + + m_locked = false; +} + +void MaterialEditorView::nodeTypeChanged(const ModelNode &node, const TypeName &, int, int) +{ + if (node == m_selectedMaterial) + delayedResetView(); +} + +void MaterialEditorView::modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap) +{ + if (node == m_selectedMaterial) + m_qmlBackEnd->updateMaterialPreview(pixmap); +} + +void MaterialEditorView::importsChanged(const QList &addedImports, const QList &removedImports) +{ + m_hasQuick3DImport = model()->hasImport("QtQuick3D"); + m_qmlBackEnd->contextObject()->setHasQuick3DImport(m_hasQuick3DImport); + + ensureMaterialLibraryNode(); // create the material lib if Quick3D import is added + resetView(); +} + +void MaterialEditorView::renameMaterial(ModelNode &material, const QString &newName) +{ + QTC_ASSERT(material.isValid(), return); + + executeInTransaction("MaterialEditorView:renameMaterial", [&] { + material.setIdWithRefactoring(generateIdFromName(newName)); + + VariantProperty objNameProp = material.variantProperty("objectName"); + objNameProp.setValue(newName); + }); +} + +void MaterialEditorView::customNotification(const AbstractView *view, const QString &identifier, + const QList &nodeList, const QList &data) +{ + if (identifier == "selected_material_changed") { + m_selectedMaterial = nodeList.first(); + QTimer::singleShot(0, this, &MaterialEditorView::resetView); + } else if (identifier == "apply_to_selected_triggered") { + applyMaterialToSelectedModels(nodeList.first(), data.first().toBool()); + } else if (identifier == "rename_material") { + if (m_selectedMaterial == nodeList.first()) + renameMaterial(m_selectedMaterial, data.first().toString()); + } else if (identifier == "add_new_material") { + handleToolBarAction(MaterialEditorContextObject::AddNewMaterial); + } +} + +// from model to material editor +void MaterialEditorView::setValue(const QmlObjectNode &qmlObjectNode, const PropertyName &name, const QVariant &value) +{ + m_locked = true; + m_qmlBackEnd->setValue(qmlObjectNode, name, value); + requestPreviewRender(); + m_locked = false; +} + +void MaterialEditorView::reloadQml() +{ + m_qmlBackendHash.clear(); + while (QWidget *widget = m_stackedWidget->widget(0)) { + m_stackedWidget->removeWidget(widget); + delete widget; + } + m_qmlBackEnd = nullptr; + + resetView(); +} + +// generate a unique camelCase id from a name +QString MaterialEditorView::generateIdFromName(const QString &name) +{ + QString newId; + if (name.isEmpty()) { + newId = "material"; + } else { + // convert to camel case + QStringList nameWords = name.split(" "); + nameWords[0] = nameWords[0].at(0).toLower() + nameWords[0].mid(1); + for (int i = 1; i < nameWords.size(); ++i) + nameWords[i] = nameWords[i].at(0).toUpper() + nameWords[i].mid(1); + newId = nameWords.join(""); + + // if id starts with a number prepend an underscore + if (newId.at(0).isDigit()) + newId.prepend('_'); + } + + QRegularExpression rgx("\\d+$"); // matches a number at the end of a string + while (hasId(newId)) { // id exists + QRegularExpressionMatch match = rgx.match(newId); + if (match.hasMatch()) { // ends with a number, increment it + QString numStr = match.captured(); + int num = numStr.toInt() + 1; + newId = newId.mid(0, match.capturedStart()) + QString::number(num); + } else { + newId.append('1'); + } + } + + return newId; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h new file mode 100644 index 00000000000..9d096064bc2 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** 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 + +QT_BEGIN_NAMESPACE +class QShortcut; +class QStackedWidget; +class QTimer; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class ModelNode; +class MaterialEditorQmlBackend; + +class MaterialEditorView : public AbstractView +{ + Q_OBJECT + +public: + MaterialEditorView(QWidget *parent = nullptr); + ~MaterialEditorView() override; + + bool hasWidget() const override; + WidgetInfo widgetInfo() override; + + void selectedNodesChanged(const QList &selectedNodeList, + const QList &lastSelectedNodeList) override; + + void propertiesRemoved(const QList &propertyList) override; + + void modelAttached(Model *model) override; + void modelAboutToBeDetached(Model *model) override; + + void variantPropertiesChanged(const QList &propertyList, PropertyChangeFlags propertyChange) override; + void bindingPropertiesChanged(const QList &propertyList, PropertyChangeFlags propertyChange) override; + void auxiliaryDataChanged(const ModelNode &node, const PropertyName &name, const QVariant &data) override; + + void resetView(); + void currentStateChanged(const ModelNode &node) override; + void instancePropertyChanged(const QList > &propertyList) override; + + void nodeTypeChanged(const ModelNode& node, const TypeName &type, int majorVersion, int minorVersion) override; + void modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap) override; + void importsChanged(const QList &addedImports, const QList &removedImports) override; + void customNotification(const AbstractView *view, const QString &identifier, + const QList &nodeList, const QList &data) override; + + void changeValue(const QString &name); + void changeExpression(const QString &name); + void exportPropertyAsAlias(const QString &name); + void removeAliasExport(const QString &name); + + bool locked() const; + + void currentTimelineChanged(const ModelNode &node) override; + +public slots: + void handleToolBarAction(int action); + +protected: + void timerEvent(QTimerEvent *event) override; + void setValue(const QmlObjectNode &fxObjectNode, const PropertyName &name, const QVariant &value); + +private: + static QString materialEditorResourcesPath(); + + void reloadQml(); + QString generateIdFromName(const QString &name); + + void ensureMaterialLibraryNode(); + void requestPreviewRender(); + void applyMaterialToSelectedModels(const ModelNode &material, bool add = false); + + void delayedResetView(); + void setupQmlBackend(); + + void commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value); + void commitAuxValueToModel(const PropertyName &propertyName, const QVariant &value); + void removePropertyFromModel(const PropertyName &propertyName); + void renameMaterial(ModelNode &material, const QString &newName); + + bool noValidSelection() const; + + ModelNode m_selectedMaterial; + ModelNode m_materialLibrary; + QShortcut *m_updateShortcut = nullptr; + int m_timerId = 0; + QStackedWidget *m_stackedWidget = nullptr; + QList m_selectedModels; + QHash m_qmlBackendHash; + MaterialEditorQmlBackend *m_qmlBackEnd = nullptr; + bool m_locked = false; + bool m_setupCompleted = false; + bool m_hasQuick3DImport = false; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index 138a4aad844..6106f901948 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -42,10 +42,10 @@ #include #include #include +#include #include #include #include - #include #include @@ -339,7 +339,8 @@ QList NavigatorTreeModel::filteredList(const NodeListProperty &proper if (filter) { list.append(Utils::filtered(nameFilteredList, [] (const ModelNode &arg) { - const bool value = QmlItemNode::isValidQmlItemNode(arg) || NodeHints::fromModelNode(arg).visibleInNavigator(); + const bool value = (QmlItemNode::isValidQmlItemNode(arg) || NodeHints::fromModelNode(arg).visibleInNavigator()) + && arg.id() != Constants::MATERIAL_LIB_ID; return value; })); } else { @@ -688,13 +689,20 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in return; bool validContainer = false; - bool showMatToCompInfo = 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()) { - ModelNode targetNode = targetProperty.parentModelNode(); ChooseFromPropertyListDialog *dialog = ChooseFromPropertyListDialog::createIfNeeded( targetNode, newModelNode, Core::ICore::dialogParent()); if (dialog) { @@ -728,17 +736,35 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in } delete dialog; } + + if (newModelNode.isSubclassOf("QtQuick3D.View3D")) { + const QList models = newModelNode.subModelNodesOfType("QtQuick3D.Model"); + + QTC_ASSERT(models.size() == 1, return); + + assignMaterialToModel(models.at(0)); + } else if (newModelNode.isSubclassOf("QtQuick3D.Model")) { + assignMaterialToModel(newModelNode); + } + + // dropping a material on a model if (newModelNode.isSubclassOf("QtQuick3D.Material") - && targetProperty.parentModelNode().isSubclassOf("QtQuick3D.Node") - && targetProperty.parentModelNode().isComponent()) { - // Inserting materials under imported components is likely a mistake, so - // notify user with a helpful messagebox that suggests the correct action. - showMatToCompInfo = true; + && 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; } if (!validContainer) { - if (!showMatToCompInfo) - validContainer = NodeHints::fromModelNode(targetProperty.parentModelNode()).canBeContainerFor(newModelNode); + validContainer = NodeHints::fromModelNode(targetProperty.parentModelNode()).canBeContainerFor(newModelNode); if (!validContainer) newQmlObjectNode.destroy(); } @@ -771,30 +797,6 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in } } } - - if (showMatToCompInfo) { - QMessageBox::StandardButton selectedButton = QMessageBox::information( - Core::ICore::dialogParent(), - QCoreApplication::translate("NavigatorTreeModel", "Warning"), - QCoreApplication::translate( - "NavigatorTreeModel", - "Inserting materials under imported 3D component nodes is not supported. " - "Materials used in imported 3D components have to be modified inside the component itself.\n\n" - "Would you like to go into component \"%1\"?") - .arg(targetProperty.parentModelNode().id()), - QMessageBox::Yes | QMessageBox::No, - QMessageBox::No); - if (selectedButton == QMessageBox::Yes) { - qint32 internalId = targetProperty.parentModelNode().internalId(); - QTimer::singleShot(0, this, [internalId, this]() { - if (!m_view.isNull() && m_view->model()) { - ModelNode node = m_view->modelNodeForInternalId(internalId); - if (node.isValid() && node.isComponent()) - DocumentManager::goIntoComponent(node); - } - }); - } - } } } @@ -1092,6 +1094,40 @@ 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 96529816076..0fcb7aab3eb 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h @@ -130,6 +130,7 @@ 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/components/propertyeditor/propertyeditorvalue.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp index c447d6c4a96..8b1eea6dc97 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp @@ -24,12 +24,15 @@ ****************************************************************************/ #include "propertyeditorvalue.h" +#include "variantproperty.h" +#include "documentmanager.h" #include #include #include -#include +#include #include +#include #include #include #include @@ -40,6 +43,7 @@ #include #include #include +#include //using namespace QmlDesigner; @@ -367,6 +371,18 @@ void PropertyEditorValue::setEnumeration(const QString &scope, const QString &na setValueWithEmit(QVariant::fromValue(newEnumeration)); } +bool PropertyEditorValue::isSupportedDrop(const QString &path) +{ + QString suffix = "*." + QFileInfo(path).suffix().toLower(); + + if (m_modelNode.isSubclassOf("QtQuick3D.Material") && nameAsQString().endsWith("Map")) + return QmlDesigner::AssetsLibraryModel::supportedImageSuffixes().contains(suffix); + + // TODO: handle support for other object properties dnd here (like image source) + + return false; +} + void PropertyEditorValue::exportPropertyAsAlias() { emit exportPropertyAsAliasRequested(nameAsQString()); @@ -499,6 +515,29 @@ bool PropertyEditorValue::idListReplace(int idx, const QString &value) return true; } +void PropertyEditorValue::commitDrop(const QString &path) +{ + if (m_modelNode.isSubclassOf("QtQuick3D.Material") && nameAsQString().endsWith("Map")) { + // 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); + // TODO: group textures under 1 node (just like materials) + + // set texture source + Utils::FilePath imagePath = Utils::FilePath::fromString(path); + Utils::FilePath currFilePath = QmlDesigner::DocumentManager::currentFilePath(); + QmlDesigner::VariantProperty srcProp = texture.variantProperty("source"); + srcProp.setValue(imagePath.relativePath(currFilePath).toUrl()); + + // assign the texture to the property + setExpressionWithEmit(texture.id()); + } +} + QStringList PropertyEditorValue::generateStringList(const QString &string) const { QString copy = string; diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h index 8e29d0ae0ef..2a2b09d5c8c 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h @@ -143,10 +143,12 @@ public: Q_INVOKABLE bool idListAdd(const QString &value); Q_INVOKABLE bool idListRemove(int idx); Q_INVOKABLE bool idListReplace(int idx, const QString &value); + Q_INVOKABLE void commitDrop(const QString &path); public slots: void resetValue(); void setEnumeration(const QString &scope, const QString &name); + bool isSupportedDrop(const QString &path); signals: void valueChanged(const QString &name, const QVariant&); diff --git a/src/plugins/qmldesigner/designercore/include/model.h b/src/plugins/qmldesigner/designercore/include/model.h index d46fbfe4930..9a2bc14fe63 100644 --- a/src/plugins/qmldesigner/designercore/include/model.h +++ b/src/plugins/qmldesigner/designercore/include/model.h @@ -125,6 +125,7 @@ public: void clearMetaInfoCache(); bool hasId(const QString &id) const; + bool hasImport(const QString &importUrl) const; QString generateNewId(const QString &prefixName) const; QString generateNewId(const QString &prefixName, const QString &fallbackPrefix) const; diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index f4ca504cc50..d879271c0d4 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -1462,6 +1462,13 @@ bool Model::hasId(const QString &id) const return d->hasId(id); } +bool Model::hasImport(const QString &importUrl) const +{ + return Utils::anyOf(imports(), [&](const Import &import) { + return import.url() == importUrl; + }); +} + static QString firstCharToLower(const QString &string) { QString resultString = string; diff --git a/src/plugins/qmldesigner/designercore/model/viewmanager.cpp b/src/plugins/qmldesigner/designercore/model/viewmanager.cpp index a2565d0c415..8ad61d320cf 100644 --- a/src/plugins/qmldesigner/designercore/model/viewmanager.cpp +++ b/src/plugins/qmldesigner/designercore/model/viewmanager.cpp @@ -42,6 +42,8 @@ #include #include #include +#include +#include #include #include #include @@ -76,6 +78,8 @@ public: ItemLibraryView itemLibraryView; NavigatorView navigatorView; PropertyEditorView propertyEditorView; + MaterialEditorView materialEditorView; + MaterialBrowserView materialBrowserView; StatesEditorView statesEditorView; std::vector> additionalViews; @@ -92,7 +96,7 @@ ViewManager::ViewManager() d->formEditorView.setGotoErrorCallback([this](int line, int column) { d->textEditorView.gotoCursorPosition(line, column); if (Internal::DesignModeWidget *designModeWidget = QmlDesignerPlugin::instance()->mainWidget()) - designModeWidget->showInternalTextEditor(); + designModeWidget->showDockWidget("TextEditor"); }); } @@ -183,6 +187,8 @@ QList ViewManager::standardViews() const &d->itemLibraryView, &d->navigatorView, &d->propertyEditorView, + &d->materialEditorView, + &d->materialBrowserView, &d->statesEditorView, &d->designerActionManagerView}; @@ -316,6 +322,8 @@ QList ViewManager::widgetInfos() const widgetInfoList.append(d->itemLibraryView.widgetInfo()); widgetInfoList.append(d->navigatorView.widgetInfo()); widgetInfoList.append(d->propertyEditorView.widgetInfo()); + widgetInfoList.append(d->materialEditorView.widgetInfo()); + widgetInfoList.append(d->materialBrowserView.widgetInfo()); widgetInfoList.append(d->statesEditorView.widgetInfo()); if (d->debugView.hasWidget()) widgetInfoList.append(d->debugView.widgetInfo()); diff --git a/src/plugins/qmldesigner/designmodewidget.cpp b/src/plugins/qmldesigner/designmodewidget.cpp index ec0303e54e3..e6ffe1c8919 100644 --- a/src/plugins/qmldesigner/designmodewidget.cpp +++ b/src/plugins/qmldesigner/designmodewidget.cpp @@ -583,9 +583,9 @@ CrumbleBar *DesignModeWidget::crumbleBar() const return m_crumbleBar; } -void DesignModeWidget::showInternalTextEditor() +void DesignModeWidget::showDockWidget(const QString &objectName) { - auto dockWidget = m_dockManager->findDockWidget("TextEditor"); + auto dockWidget = m_dockManager->findDockWidget(objectName); if (dockWidget) dockWidget->toggleView(true); } diff --git a/src/plugins/qmldesigner/designmodewidget.h b/src/plugins/qmldesigner/designmodewidget.h index bf348d33006..6f6f7283b0e 100644 --- a/src/plugins/qmldesigner/designmodewidget.h +++ b/src/plugins/qmldesigner/designmodewidget.h @@ -79,14 +79,14 @@ public: void enableWidgets(); void disableWidgets(); - CrumbleBar* crumbleBar() const; - void showInternalTextEditor(); + CrumbleBar *crumbleBar() const; + void showDockWidget(const QString &objectName); void determineWorkspaceToRestoreAtStartup(); static QWidget *createProjectExplorerWidget(QWidget *parent); -private: // functions +private: enum InitializeStatus { NotInitialized, Initializing, Initialized }; void toolBarOnGoBackClicked(); @@ -101,7 +101,6 @@ private: // functions void aboutToShowWorkspaces(); -private: // variables QPointer m_bottomSideBar; Core::EditorToolBar *m_toolBar; CrumbleBar *m_crumbleBar; diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index d975fbd41c7..efefc381a1f 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -84,6 +84,7 @@ const char QUICK_3D_ASSET_IMPORT_DATA_NAME[] = "_importdata.json"; const char QUICK_3D_ASSET_IMPORT_DATA_OPTIONS_KEY[] = "import_options"; const char QUICK_3D_ASSET_IMPORT_DATA_SOURCE_KEY[] = "source_scene"; const char DEFAULT_ASSET_IMPORT_FOLDER[] = "/asset_imports"; +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"; diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index c8597a2d8a4..ff6cc797164 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -488,7 +488,7 @@ Project { "componentcore/formatoperation.h", "componentcore/layoutingridlayout.cpp", "componentcore/layoutingridlayout.h", - "componentcore/theme.cpp", + "componentcore/theme.cpp", "componentcore/theme.h", "componentcore/modelnodecontextmenu.cpp", "componentcore/modelnodecontextmenu.h", @@ -675,6 +675,20 @@ Project { "itemlibrary/itemlibrarywidget.h", "itemlibrary/itemlibraryiconimageprovider.cpp", "itemlibrary/itemlibraryiconimageprovider.h", + "materialbrowser/materialbrowsermodel.cpp", + "materialbrowser/materialbrowsermodel.h", + "materialbrowser/materialbrowserview.cpp", + "materialbrowser/materialbrowserview.h", + "materialbrowser/materialbrowserwidget.cpp", + "materialbrowser/materialbrowserwidget.h", + "materialeditor/materialeditorcontextobject.cpp", + "materialeditor/materialeditorcontextobject.h", + "materialeditor/materialeditorqmlbackend.cpp", + "materialeditor/materialeditorqmlbackend.h", + "materialeditor/materialeditortransaction.cpp", + "materialeditor/materialeditortransaction.h", + "materialeditor/materialeditorview.cpp", + "materialeditor/materialeditorview.h", "navigator/iconcheckboxitemdelegate.cpp", "navigator/iconcheckboxitemdelegate.h", "navigator/nameitemdelegate.cpp", From 37445824e36746d6b2522e4860ccf4fc3c8ca87f Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Tue, 24 May 2022 07:49:54 +0200 Subject: [PATCH 02/11] QmlDesigner: Fix puppet build for Qt5 Amends f09d4538e73dfd8. Change-Id: Ibff8b834e65fd18f6345431c8729b7592eab0c5c Reviewed-by: Reviewed-by: Mahmoud Badri --- .../commands/requestmodelnodepreviewimagecommand.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/share/qtcreator/qml/qmlpuppet/commands/requestmodelnodepreviewimagecommand.h b/share/qtcreator/qml/qmlpuppet/commands/requestmodelnodepreviewimagecommand.h index 773656048cd..9979bccd783 100644 --- a/share/qtcreator/qml/qmlpuppet/commands/requestmodelnodepreviewimagecommand.h +++ b/share/qtcreator/qml/qmlpuppet/commands/requestmodelnodepreviewimagecommand.h @@ -64,9 +64,15 @@ inline bool operator==(const RequestModelNodePreviewImageCommand &first, && first.renderItemId() == second.renderItemId(); } -inline size_t qHash(const RequestModelNodePreviewImageCommand &key, size_t seed) +inline size_t qHash(const RequestModelNodePreviewImageCommand &key, size_t seed = 0) { +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + return ::qHash(key.instanceId(), seed) + ^ ::qHash(std::make_pair(key.size().width(), key.size().height()), seed) + ^ ::qHash(key.componentPath(), seed) ^ ::qHash(key.renderItemId(), seed); +#else return qHashMulti(seed, key.instanceId(), key.size(), key.componentPath(), key.renderItemId()); +#endif } QDataStream &operator<<(QDataStream &out, const RequestModelNodePreviewImageCommand &command); From 80609733a5d6e3a5371b7c3243a4e7f42f5ade37 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Tue, 24 May 2022 09:57:47 +0200 Subject: [PATCH 03/11] QmlDesigner: Fix build with Qbs Amends f09d4538e73. Change-Id: I34579b59181c81150934b63dfe8a69e6488df816 Reviewed-by: Mahmoud Badri --- src/plugins/qmldesigner/qmldesignerplugin.qbs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index ff6cc797164..1326701ef72 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -62,6 +62,8 @@ Project { "components/texteditor", "components/timelineeditor", "components/listmodeleditor", + "components/materialbrowser", + "components/materialeditor", ]) Properties { From d66b65b74bffacee1c5886feb55e8a190ffcb0b6 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Mon, 23 May 2022 20:20:38 +0300 Subject: [PATCH 04/11] QmlDesigner: Maintain asset thumbnail's aspect ratio Change-Id: I22aa85afbe7bd56df8cf80f792b6bfb93ec01541 Reviewed-by: Reviewed-by: Miikka Heikkinen --- share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml index a8d0a53381b..54290223aa6 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml @@ -644,6 +644,7 @@ Item { Image { id: img asynchronous: true + fillMode: Image.PreserveAspectFit width: 48 height: 48 source: "image://qmldesigner_assets/" + filePath From cb93846367066ab45ec479066ff5cf22ce2a4126 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Mon, 23 May 2022 20:32:43 +0300 Subject: [PATCH 05/11] QmlDesigner: Clear components view search box upon load i.e. upon changing from edit mode to QDS mode. This also removed a warning. Change-Id: Ibd3ee3bd30febfcee9a05cb87047e79b8ed1bf61 Reviewed-by: Reviewed-by: Miikka Heikkinen --- .../qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml | 2 +- .../qmldesigner/itemLibraryQmlSources/ItemsView.qml | 6 ++++++ .../materialBrowserQmlSource/MaterialBrowser.qml | 2 +- .../imports/HelperWidgets/SearchBox.qml | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml index 54290223aa6..b63280b3681 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml @@ -48,7 +48,7 @@ Item { function clearSearchFilter() { - searchBox.text = ""; + searchBox.clear(); } function updateDropExtFiles(drag) diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemsView.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemsView.qml index 4771127ab82..ae39fdb5e3e 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemsView.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemsView.qml @@ -91,6 +91,12 @@ Item { itemContextMenu.close() } + // Called from C++ + function clearSearchFilter() + { + searchBox.clear(); + } + // Called also from C++ function switchToComponentsView() { diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index 3fdc9cffdac..1518e477e39 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -56,7 +56,7 @@ Item { // Called from C++ function clearSearchFilter() { - searchBox.clearSearchFilter(); + searchBox.clear(); } MouseArea { diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SearchBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SearchBox.qml index d1b6001bcf6..8f0079249db 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SearchBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SearchBox.qml @@ -33,7 +33,7 @@ Item { property alias text: searchFilterText.text - function clearSearchFilter() + function clear() { searchFilterText.text = ""; } From c5818fc844c5ac65b771b31dcd96e07c362b13e1 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Tue, 24 May 2022 11:51:27 +0200 Subject: [PATCH 06/11] Update change log for 7.0.2 Add some things that were fixes outside of the Qt Creator main repository. Change-Id: Ic468fcf9f3daaba6b8a1efc760b4f757aec1fc1f Reviewed-by: Cristian Adam Reviewed-by: Reviewed-by: Leena Miettinen --- dist/changes-7.0.2.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dist/changes-7.0.2.md b/dist/changes-7.0.2.md index 4741a483152..f8966193875 100644 --- a/dist/changes-7.0.2.md +++ b/dist/changes-7.0.2.md @@ -25,6 +25,7 @@ Editing ### C++ +* Updated to LLVM 14.0.3 * Fixed wrong `__cplusplus` value for older GCC versions * ClangFormat * Fixed disappearing settings drop down (QTCREATORBUG-26948) @@ -56,6 +57,7 @@ Platforms ### macOS +* Fixed that `arm_neon.h` could not be found by code model (QTCREATORBUG-27455) * Fixed compilier identification of `cc` and `c++` (QTCREATORBUG-27523) Credits for these changes go to: From 71e553f4974b2fb95b56d07ce3e35f4130ef3b64 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Tue, 24 May 2022 14:14:14 +0300 Subject: [PATCH 07/11] QmlDesigner: Disable apply material to selected when no selection exist When there is no valid model selected, disable the "apply to selected" actions in the material editor and browser. Change-Id: Id8e771c64e69c0ba2f42dff01d19ffbf4afafb77 Reviewed-by: Reviewed-by: Thomas Hartmann --- .../MaterialBrowser.qml | 4 ++-- .../MaterialEditorToolBar.qml | 2 +- .../materialbrowser/materialbrowsermodel.cpp | 20 ++++++++++++++----- .../materialbrowser/materialbrowsermodel.h | 6 ++++++ .../materialbrowser/materialbrowserview.cpp | 10 ++++++---- .../materialeditorcontextobject.cpp | 14 +++++++++++++ .../materialeditorcontextobject.h | 6 ++++++ .../materialeditor/materialeditorview.cpp | 2 ++ 8 files changed, 52 insertions(+), 12 deletions(-) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index 1518e477e39..f0d8cf7e732 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -92,13 +92,13 @@ Item { StudioControls.MenuItem { text: qsTr("Apply to selected (replace)") - enabled: currentMaterial + enabled: currentMaterial && materialBrowserModel.hasModelSelection onTriggered: materialBrowserModel.applyToSelected(currentMaterial.materialInternalId, false) } StudioControls.MenuItem { text: qsTr("Apply to selected (add)") - enabled: currentMaterial + enabled: currentMaterial && materialBrowserModel.hasModelSelection onTriggered: materialBrowserModel.applyToSelected(currentMaterial.materialInternalId, true) } diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml index 028957c882f..c610df94c5e 100644 --- a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorToolBar.qml @@ -50,7 +50,7 @@ Rectangle { normalColor: "transparent" iconSize: StudioTheme.Values.bigIconFontSize buttonSize: root.height - enabled: hasMaterial && hasQuick3DImport + enabled: hasMaterial && hasModelSelection && hasQuick3DImport onClicked: root.toolBarAction(ToolBarAction.ApplyToSelected) tooltip: qsTr("Apply material to selected model.") } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp index 012b540914c..005b6ed5ddc 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp @@ -106,6 +106,20 @@ void MaterialBrowserModel::setHasQuick3DImport(bool b) emit hasQuick3DImportChanged(); } +bool MaterialBrowserModel::hasModelSelection() const +{ + return m_hasModelSelection; +} + +void MaterialBrowserModel::setHasModelSelection(bool b) +{ + if (b == m_hasModelSelection) + return; + + m_hasModelSelection = b; + emit hasModelSelectionChanged(); +} + void MaterialBrowserModel::setSearchText(const QString &searchText) { QString lowerSearchText = searchText.toLower(); @@ -156,11 +170,7 @@ void MaterialBrowserModel::setMaterials(const QList &materials, bool emit isEmptyChanged(); } - if (hasQuick3DImport != m_hasQuick3DImport) { - m_hasQuick3DImport = hasQuick3DImport; - emit hasQuick3DImportChanged(); - } - + setHasQuick3DImport(hasQuick3DImport); updateSelectedMaterial(); resetModel(); } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h index 7d8fa15121b..5f00596ef9e 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h @@ -40,6 +40,7 @@ class MaterialBrowserModel : public QAbstractListModel Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) + Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged) public: MaterialBrowserModel(QObject *parent = nullptr); @@ -54,6 +55,9 @@ public: bool hasQuick3DImport() const; void setHasQuick3DImport(bool b); + bool hasModelSelection() const; + void setHasModelSelection(bool b); + void setMaterials(const QList &materials, bool hasQuick3DImport); void removeMaterial(const ModelNode &material); void updateMaterialName(const ModelNode &material); @@ -73,6 +77,7 @@ public: signals: void isEmptyChanged(); void hasQuick3DImportChanged(); + void hasModelSelectionChanged(); void selectedIndexChanged(int idx); void renameMaterialTriggered(const QmlDesigner::ModelNode &material, const QString &newName); void applyToSelectedTriggered(const QmlDesigner::ModelNode &material, bool add = false); @@ -89,6 +94,7 @@ private: int m_selectedIndex = 0; bool m_isEmpty = true; bool m_hasQuick3DImport = false; + bool m_hasModelSelection = false; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 0946411c9b5..3239d2f302b 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -118,10 +118,6 @@ void MaterialBrowserView::modelAboutToBeDetached(Model *model) void MaterialBrowserView::selectedNodesChanged(const QList &selectedNodeList, const QList &lastSelectedNodeList) { - if (!m_autoSelectModelMaterial) - return; - - // if selected object is a model, select its material in the material browser and editor ModelNode selectedModel; for (const ModelNode &node : selectedNodeList) { @@ -131,6 +127,11 @@ void MaterialBrowserView::selectedNodesChanged(const QList &selectedN } } + m_widget->materialBrowserModel()->setHasModelSelection(selectedModel.isValid()); + + if (!m_autoSelectModelMaterial) + return; + if (selectedNodeList.size() > 1 || !selectedModel.isValid()) return; @@ -144,6 +145,7 @@ void MaterialBrowserView::selectedNodesChanged(const QList &selectedN if (!mat.isValid()) return; + // if selected object is a model, select its material in the material browser and editor int idx = m_widget->materialBrowserModel()->materialIndex(mat); m_widget->materialBrowserModel()->selectMaterial(idx); } diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp index bf365bf270e..435c5318979 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp @@ -233,6 +233,20 @@ void MaterialEditorContextObject::setHasQuick3DImport(bool b) emit hasQuick3DImportChanged(); } +bool MaterialEditorContextObject::hasModelSelection() const +{ + return m_hasModelSelection; +} + +void MaterialEditorContextObject::setHasModelSelection(bool b) +{ + if (b == m_hasModelSelection) + return; + + m_hasModelSelection = b; + emit hasModelSelectionChanged(); +} + void MaterialEditorContextObject::setSelectedMaterial(const ModelNode &matNode) { m_selectedMaterial = matNode; diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h index c1b9a880462..54d72171a56 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.h @@ -55,6 +55,7 @@ class MaterialEditorContextObject : public QObject Q_PROPERTY(bool hasAliasExport READ hasAliasExport NOTIFY hasAliasExportChanged) Q_PROPERTY(bool hasActiveTimeline READ hasActiveTimeline NOTIFY hasActiveTimelineChanged) Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) + Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged) Q_PROPERTY(QQmlPropertyMap *backendValues READ backendValues WRITE setBackendValues NOTIFY backendValuesChanged) @@ -104,6 +105,9 @@ public: bool hasQuick3DImport() const; void setHasQuick3DImport(bool b); + bool hasModelSelection() const; + void setHasModelSelection(bool b); + bool hasAliasExport() const { return m_aliasExport; } void setSelectedMaterial(const ModelNode &matNode); @@ -130,6 +134,7 @@ signals: void hasAliasExportChanged(); void hasActiveTimelineChanged(); void hasQuick3DImportChanged(); + void hasModelSelectionChanged(); private: QUrl m_specificsUrl; @@ -150,6 +155,7 @@ private: bool m_aliasExport = false; bool m_hasActiveTimeline = false; bool m_hasQuick3DImport = false; + bool m_hasModelSelection = false; ModelNode m_selectedMaterial; }; diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index b72b91117fb..5f115b67778 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -677,6 +677,8 @@ void MaterialEditorView::selectedNodesChanged(const QList &selectedNo if (node.isSubclassOf("QtQuick3D.Model")) m_selectedModels.append(node); } + + m_qmlBackEnd->contextObject()->setHasModelSelection(!m_selectedModels.isEmpty()); } void MaterialEditorView::currentStateChanged(const ModelNode &node) From 4c1e4ed9077a8fa024af6c19a6806ecf8babfdd9 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Tue, 24 May 2022 14:43:18 +0300 Subject: [PATCH 08/11] QmlDesigner: Unify empty text between material editor and browser Make the text indicating that there are no materials, same size and content between the material editor and browser. Change-Id: I6e9e46c30d89aed090b504b256b113e2601b4659 Reviewed-by: Miikka Heikkinen --- .../qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml | 3 ++- .../materialEditorQmlSources/EmptyMaterialEditorPane.qml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index f0d8cf7e732..77eedc67de1 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -163,9 +163,10 @@ Item { } Text { - text: qsTr("No materials yet."); + text: qsTr("No materials yet.\nClick '+' above to start.") color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.mediumFontSize + horizontalAlignment: Text.AlignHCenter topPadding: 30 anchors.horizontalCenter: parent.horizontalCenter visible: materialBrowserModel.hasQuick3DImport && materialBrowserModel.isEmpty && searchBox.isEmpty() diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml index 2e812dcd661..80ec524e23e 100644 --- a/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/EmptyMaterialEditorPane.qml @@ -48,10 +48,10 @@ PropertyEditorPane { height: 150 Text { - text: hasQuick3DImport ? qsTr("No materials yet.\nClick 'Add new material' above to start.") + text: hasQuick3DImport ? qsTr("No materials yet.\nClick '+' above to start.") : qsTr("Add QtQuick3D module using the Components view to enable the Material Editor.") color: StudioTheme.Values.themeTextColor - font.pixelSize: StudioTheme.Values.baseFontSize + font.pixelSize: StudioTheme.Values.mediumFontSize horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap width: root.width From 917ae81ce6ed62ca0c477ecff12755334df61a8c Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Tue, 24 May 2022 16:04:50 +0300 Subject: [PATCH 09/11] QmlDesigner: focus material browser upon open When clicking the open material browser button in the material editor, focus the material browser window if it is already open. Also do the same when opening the material editor by double clicking a material in the material browser. Fixes: QDS-7006 Change-Id: If64d1ff6595dea5e33771485622322df708405d9 Reviewed-by: Miikka Heikkinen --- .../components/materialbrowser/materialbrowsermodel.cpp | 2 +- .../components/materialeditor/materialeditorview.cpp | 2 +- src/plugins/qmldesigner/designmodewidget.cpp | 8 ++++++-- src/plugins/qmldesigner/designmodewidget.h | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp index 005b6ed5ddc..d4bee4fcced 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp @@ -275,7 +275,7 @@ void MaterialBrowserModel::applyToSelected(qint64 internalId, bool add) void MaterialBrowserModel::openMaterialEditor() { - QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialEditor"); + QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialEditor", true); } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index 5f115b67778..c28152276c4 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -459,7 +459,7 @@ void MaterialEditorView::handleToolBarAction(int action) } case MaterialEditorContextObject::OpenMaterialBrowser: { - QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialBrowser"); + QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialBrowser", true); break; } } diff --git a/src/plugins/qmldesigner/designmodewidget.cpp b/src/plugins/qmldesigner/designmodewidget.cpp index e6ffe1c8919..51dbd58c990 100644 --- a/src/plugins/qmldesigner/designmodewidget.cpp +++ b/src/plugins/qmldesigner/designmodewidget.cpp @@ -583,11 +583,15 @@ CrumbleBar *DesignModeWidget::crumbleBar() const return m_crumbleBar; } -void DesignModeWidget::showDockWidget(const QString &objectName) +void DesignModeWidget::showDockWidget(const QString &objectName, bool focus) { auto dockWidget = m_dockManager->findDockWidget(objectName); - if (dockWidget) + if (dockWidget) { dockWidget->toggleView(true); + + if (focus) + dockWidget->setFocus(); + } } void DesignModeWidget::contextHelp(const Core::IContext::HelpCallback &callback) const diff --git a/src/plugins/qmldesigner/designmodewidget.h b/src/plugins/qmldesigner/designmodewidget.h index 6f6f7283b0e..643c8df8e47 100644 --- a/src/plugins/qmldesigner/designmodewidget.h +++ b/src/plugins/qmldesigner/designmodewidget.h @@ -80,7 +80,7 @@ public: void disableWidgets(); CrumbleBar *crumbleBar() const; - void showDockWidget(const QString &objectName); + void showDockWidget(const QString &objectName, bool focus = false); void determineWorkspaceToRestoreAtStartup(); From 5e27a964465015986f72806b664715e8bf31344e Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Tue, 24 May 2022 15:37:35 +0200 Subject: [PATCH 10/11] Bump version to 7.0.3 Change-Id: I861b3b9c5616297af12e04b39bd157107a27055b Reviewed-by: Eike Ziller --- cmake/QtCreatorIDEBranding.cmake | 4 ++-- qbs/modules/qtc/qtc.qbs | 4 ++-- qtcreator_ide_branding.pri | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmake/QtCreatorIDEBranding.cmake b/cmake/QtCreatorIDEBranding.cmake index 2efb4fb1ae1..660159e7a7a 100644 --- a/cmake/QtCreatorIDEBranding.cmake +++ b/cmake/QtCreatorIDEBranding.cmake @@ -1,6 +1,6 @@ -set(IDE_VERSION "7.0.2") # The IDE version. +set(IDE_VERSION "7.0.3") # The IDE version. set(IDE_VERSION_COMPAT "7.0.0") # The IDE Compatibility version. -set(IDE_VERSION_DISPLAY "7.0.2") # The IDE display version. +set(IDE_VERSION_DISPLAY "7.0.3") # The IDE display version. set(IDE_COPYRIGHT_YEAR "2022") # The IDE current copyright year. set(IDE_SETTINGSVARIANT "QtProject") # The IDE settings variation. diff --git a/qbs/modules/qtc/qtc.qbs b/qbs/modules/qtc/qtc.qbs index dd881514e4b..e505ed2814f 100644 --- a/qbs/modules/qtc/qtc.qbs +++ b/qbs/modules/qtc/qtc.qbs @@ -3,10 +3,10 @@ import qbs.Environment import qbs.FileInfo Module { - property string qtcreator_display_version: '7.0.2' + property string qtcreator_display_version: '7.0.3' property string ide_version_major: '7' property string ide_version_minor: '0' - property string ide_version_release: '2' + property string ide_version_release: '3' property string qtcreator_version: ide_version_major + '.' + ide_version_minor + '.' + ide_version_release diff --git a/qtcreator_ide_branding.pri b/qtcreator_ide_branding.pri index ff54a16053e..2b86c06a55a 100644 --- a/qtcreator_ide_branding.pri +++ b/qtcreator_ide_branding.pri @@ -1,6 +1,6 @@ -QTCREATOR_VERSION = 7.0.2 +QTCREATOR_VERSION = 7.0.3 QTCREATOR_COMPAT_VERSION = 7.0.0 -QTCREATOR_DISPLAY_VERSION = 7.0.2 +QTCREATOR_DISPLAY_VERSION = 7.0.3 QTCREATOR_COPYRIGHT_YEAR = 2022 IDE_DISPLAY_NAME = Qt Creator From 07d9075d478163fa15b24da424d9948d1d46ae05 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Tue, 24 May 2022 16:54:52 +0200 Subject: [PATCH 11/11] QmlDesigner: Add QtQuick 6.3 as supported version Change-Id: Icbb1d2a3c7584cc24f0d8dde467d07678925711c Reviewed-by: Thomas Hartmann --- .../designercore/model/texttomodelmerger.cpp | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp index 6eb2faa02e6..176e0717ff9 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp @@ -89,25 +89,9 @@ bool isSupportedAttachedProperties(const QString &propertyName) QStringList supportedVersionsList() { - static const QStringList list = {"2.0", - "2.1", - "2.2", - "2.3", - "2.4", - "2.5", - "2.6", - "2.7", - "2.8", - "2.9", - "2.10", - "2.11", - "2.12", - "2.13", - "2.14", - "2.15", - "6.0", - "6.1", - "6.2"}; + static const QStringList list = {"2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6", + "2.7", "2.8", "2.9", "2.10", "2.11", "2.12", "2.13", + "2.14", "2.15", "6.0", "6.1", "6.2", "6.3"}; return list; }