From f09d4538e73dfd86e748d62fe8dba56c5a716cc7 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Fri, 18 Mar 2022 17:28:28 +0200 Subject: [PATCH] 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",