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 00000000000..d8550f80449
Binary files /dev/null and b/share/qtcreator/qml/qmlpuppet/mockfiles/images/floor_tex.png differ
diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/MaterialNodeView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/MaterialNodeView.qml
index 48ed09cba70..06320524bf3 100644
--- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/MaterialNodeView.qml
+++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/MaterialNodeView.qml
@@ -45,20 +45,48 @@ View3D {
Node {
DirectionalLight {
- eulerRotation.x: -30
- eulerRotation.y: -30
+ shadowMapQuality: Light.ShadowMapQualityMedium
+ shadowFilter: 20
+ shadowFactor: 21
+ castsShadow: true
+ eulerRotation.x: -26
+ eulerRotation.y: -57
}
PerspectiveCamera {
+ y: 125.331
z: 120
- clipFar: 1000
+ eulerRotation.x: -31
clipNear: 1
+ clipFar: 1000
}
Model {
id: model
+
+ y: 50
source: "#Sphere"
materials: previewMaterial
}
+
+ Model {
+ id: floorModel
+ source: "#Rectangle"
+ scale.y: 8
+ scale.x: 8
+ eulerRotation.x: -90
+ materials: floorMaterial
+ DefaultMaterial {
+ id: floorMaterial
+ diffuseMap: floorTex
+
+ Texture {
+ id: floorTex
+ source: "../images/floor_tex.png"
+ scaleU: floorModel.scale.x
+ scaleV: floorModel.scale.y
+ }
+ }
+ }
}
}
diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/MaterialNodeView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/MaterialNodeView.qml
index 0b6c7bd2144..aae5891e082 100644
--- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/MaterialNodeView.qml
+++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/MaterialNodeView.qml
@@ -45,21 +45,49 @@ View3D {
Node {
DirectionalLight {
- eulerRotation.x: -30
- eulerRotation.y: -30
+ shadowMapQuality: Light.ShadowMapQualityMedium
+ shadowFilter: 20
+ shadowFactor: 21
+ castsShadow: true
+ eulerRotation.x: -26
+ eulerRotation.y: -57
}
PerspectiveCamera {
+ y: 125.331
z: 120
- clipFar: 1000
+ eulerRotation.x: -31
clipNear: 1
+ clipFar: 1000
}
Model {
id: model
readonly property bool _edit3dLocked: true // Make this non-pickable
+
+ y: 50
source: "#Sphere"
materials: previewMaterial
}
+
+ Model {
+ id: floorModel
+ source: "#Rectangle"
+ scale.y: 8
+ scale.x: 8
+ eulerRotation.x: -90
+ materials: floorMaterial
+ DefaultMaterial {
+ id: floorMaterial
+ diffuseMap: floorTex
+
+ Texture {
+ id: floorTex
+ source: "../images/floor_tex.png"
+ scaleU: floorModel.scale.x
+ scaleV: floorModel.scale.y
+ }
+ }
+ }
}
}
diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp
index 1d266353db8..c3fc4d4e076 100644
--- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp
+++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp
@@ -1030,27 +1030,32 @@ void Qt5InformationNodeInstanceServer::doRender3DEditView()
void Qt5InformationNodeInstanceServer::renderModelNodeImageView()
{
if (!m_renderModelNodeImageViewTimer.isActive())
- m_renderModelNodeImageViewTimer.start(0);
+ m_renderModelNodeImageViewTimer.start(17);
}
void Qt5InformationNodeInstanceServer::doRenderModelNodeImageView()
{
// This crashes on Qt 6.0.x due to QtQuick3D issue, so the preview generation is disabled
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) || QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
+ 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 25c4b1e4432..5a3e99d4590 100644
Binary files a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf and b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf differ
diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt
index 733051f3089..6a13cfd2211 100644
--- a/src/plugins/qmldesigner/CMakeLists.txt
+++ b/src/plugins/qmldesigner/CMakeLists.txt
@@ -312,6 +312,23 @@ extend_qtc_plugin(QmlDesigner
quick2propertyeditorview.cpp quick2propertyeditorview.h
)
+extend_qtc_plugin(QmlDesigner
+ SOURCES_PREFIX components/materialeditor
+ SOURCES
+ materialeditorcontextobject.cpp materialeditorcontextobject.h
+ materialeditorqmlbackend.cpp materialeditorqmlbackend.h
+ materialeditortransaction.cpp materialeditortransaction.h
+ materialeditorview.cpp materialeditorview.h
+)
+
+extend_qtc_plugin(QmlDesigner
+ SOURCES_PREFIX components/materialbrowser
+ SOURCES
+ materialbrowserview.cpp materialbrowserview.h
+ materialbrowserwidget.cpp materialbrowserwidget.h
+ materialbrowsermodel.cpp materialbrowsermodel.h
+)
+
extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components
SOURCES resources/resources.qrc
diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h
index 6911ab27c7a..a70e4f81c27 100644
--- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h
+++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h
@@ -80,6 +80,7 @@ const char mergeTemplateCommandId[] = "MergeTemplate";
const char goToImplementationCommandId[] = "GoToImplementation";
const char addSignalHandlerCommandId[] = "AddSignalHandler";
const char moveToComponentCommandId[] = "MoveToComponent";
+const char editMaterialCommandId[] = "EditMaterial";
const char addItemToStackedContainerCommandId[] = "AddItemToStackedContainer";
const char addTabBarToStackedContainerCommandId[] = "AddTabBarToStackedContainer";
const char increaseIndexOfStackedContainerCommandId[] = "IncreaseIndexOfStackedContainer";
@@ -134,6 +135,7 @@ const char mergeTemplateDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMen
const char goToImplementationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Go to Implementation");
const char addSignalHandlerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Add New Signal Handler");
const char moveToComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Move Component into Separate File");
+const char editMaterialDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Material");
const char editAnnotationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Annotation");
const char openSignalDialogDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Signal Dialog");
diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp
index 959ba6772f0..7f5649ea5f6 100644
--- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp
@@ -1506,6 +1506,17 @@ void DesignerActionManager::createDefaultDesignerActions()
&singleSelection,
&singleSelection));
+ addDesignerAction(new ModelNodeContextMenuAction(
+ editMaterialCommandId,
+ editMaterialDisplayName,
+ {},
+ rootCategory,
+ QKeySequence(),
+ 44,
+ &editMaterial,
+ &modelHasMaterial,
+ &isModel));
+
addDesignerAction(new ModelNodeContextMenuAction(mergeTemplateCommandId,
mergeTemplateDisplayName,
{},
diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h
index dc36da3dc6e..686ff8b17c5 100644
--- a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h
+++ b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h
@@ -27,6 +27,7 @@
#include "modelnodeoperations.h"
#include "abstractaction.h"
+#include "bindingproperty.h"
#include "abstractactiongroup.h"
#include "qmlitemnode.h"
#include
@@ -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