diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/BundleMaterialItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/BundleMaterialItem.qml new file mode 100644 index 00000000000..4b8b45be564 --- /dev/null +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/BundleMaterialItem.qml @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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 + +Item { + id: root + + signal showContextMenu() + + visible: modelData.bundleMaterialVisible + + MouseArea { + id: mouseArea + + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onPressed: (mouse) => { + if (mouse.button === Qt.LeftButton) + rootView.startDragBundleMaterial(modelData, mapToGlobal(mouse.x, mouse.y)) + else if (mouse.button === Qt.RightButton) + root.showContextMenu() + } + } + + 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: modelData.bundleMaterialIcon + cache: false + } + + TextInput { + id: matName + + text: modelData.bundleMaterialName + + 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 + } + } +} diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index a8692f3ef9d..d9d207b555f 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -38,13 +38,15 @@ Item { property var currentMaterial: null property int currentMaterialIdx: 0 + property var currentBundleMaterial: null property var matSectionsModel: [] // Called also from C++ to close context menu on focus out function closeContextMenu() { - contextMenu.close() + cxtMenu.close() + cxtMenuBundle.close() } // Called from C++ to refresh a preview material after it changes @@ -68,10 +70,15 @@ Item { acceptedButtons: Qt.RightButton - onClicked: { - if (!materialBrowserModel.hasMaterialRoot) { + onClicked: (mouse) => { + // root context-menu works only for user materials + var userMatsSecBottom = mapFromItem(userMaterialsSection, 0, userMaterialsSection.y).y + + userMaterialsSection.height; + + if (!materialBrowserModel.hasMaterialRoot && (!materialBrowserBundleModel.matBundleExists + || mouse.y < userMatsSecBottom)) { root.currentMaterial = null - contextMenu.popup() + cxtMenu.popup() } } } @@ -90,7 +97,7 @@ Item { } StudioControls.Menu { - id: contextMenu + id: cxtMenu closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside @@ -188,6 +195,32 @@ Item { } } + StudioControls.Menu { + id: cxtMenuBundle + + closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside + + StudioControls.MenuItem { + text: qsTr("Apply to selected (replace)") + enabled: root.currentBundleMaterial && materialBrowserModel.hasModelSelection + onTriggered: materialBrowserBundleModel.applyToSelected(root.currentBundleMaterial, false) + } + + StudioControls.MenuItem { + text: qsTr("Apply to selected (add)") + enabled: root.currentBundleMaterial && materialBrowserModel.hasModelSelection + onTriggered: materialBrowserBundleModel.applyToSelected(root.currentBundleMaterial, true) + } + + StudioControls.MenuSeparator {} + + StudioControls.MenuItem { + text: qsTr("Add to project") + + onTriggered: materialBrowserBundleModel.addMaterial(root.currentBundleMaterial) + } + } + Column { id: col y: 5 @@ -215,23 +248,12 @@ Item { } } - Text { - text: qsTr("No match found."); - color: StudioTheme.Values.themeTextColor - font.pixelSize: StudioTheme.Values.baseFontSize - leftPadding: 10 - visible: materialBrowserModel.hasQuick3DImport && materialBrowserModel.isEmpty - && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot - } - Text { text: { if (materialBrowserModel.hasMaterialRoot) qsTr("Material Browser is disabled inside a material component.") else if (!materialBrowserModel.hasQuick3DImport) qsTr("To use Material Browser, first add the QtQuick3D module in the Components view.") - else if (materialBrowserModel.isEmpty && searchBox.isEmpty()) - qsTr("There are no materials in this project.
Select '+' to create one.") else "" } @@ -252,31 +274,115 @@ Item { width: root.width height: root.height - searchBox.height clip: true + visible: materialBrowserModel.hasQuick3DImport && !materialBrowserModel.hasMaterialRoot - Grid { - id: grid + Column { + Section { + id: userMaterialsSection - width: scrollView.width - leftPadding: 5 - rightPadding: 5 - bottomPadding: 5 - columns: root.width / root.cellWidth + width: root.width + caption: qsTr("User materials") + hideHeader: !materialBrowserBundleModel.matBundleExists - Repeater { - id: gridRepeater + Grid { + id: grid - model: materialBrowserModel - delegate: MaterialItem { - width: root.cellWidth - height: root.cellHeight + width: scrollView.width + leftPadding: 5 + rightPadding: 5 + bottomPadding: 5 + columns: root.width / root.cellWidth - onShowContextMenu: { - if (searchBox.isEmpty()) { - root.currentMaterial = model - contextMenu.popup() + Repeater { + id: gridRepeater + + model: materialBrowserModel + delegate: MaterialItem { + width: root.cellWidth + height: root.cellHeight + + onShowContextMenu: { + if (searchBox.isEmpty()) { + root.currentMaterial = model + cxtMenu.popup() + } + } } } } + + Text { + text: qsTr("No match found."); + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.baseFontSize + leftPadding: 10 + visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot + } + + Text { + text:qsTr("There are no materials in this project.
Select '+' to create one.") + visible: materialBrowserModel.isEmpty && searchBox.isEmpty() + textFormat: Text.RichText + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.mediumFontSize + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + width: root.width + } + } + + Section { + width: root.width + caption: qsTr("Material Library") + addTopPadding: noMatchText.visible + visible: materialBrowserBundleModel.matBundleExists + + Column { + Repeater { + model: materialBrowserBundleModel + + delegate: Section { + width: root.width + caption: bundleCategory + addTopPadding: false + sectionBackgroundColor: "transparent" + visible: bundleCategoryVisible + + Grid { + width: scrollView.width + leftPadding: 5 + rightPadding: 5 + bottomPadding: 5 + columns: root.width / root.cellWidth + + Repeater { + model: bundleMaterialsModel + + delegate: BundleMaterialItem { + width: root.cellWidth + height: root.cellHeight + + onShowContextMenu: { + if (searchBox.isEmpty()) { + root.currentBundleMaterial = modelData + cxtMenuBundle.popup() + } + } + } + } + } + } + } + + Text { + id: noMatchText + text: qsTr("No match found."); + color: StudioTheme.Values.themeTextColor + font.pixelSize: StudioTheme.Values.baseFontSize + leftPadding: 10 + visible: materialBrowserBundleModel.isEmpty && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot + } + } } } } diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index bcd959cf0d8..5f025ff3706 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -337,6 +337,9 @@ extend_qtc_plugin(QmlDesigner materialbrowserwidget.cpp materialbrowserwidget.h materialbrowsermodel.cpp materialbrowsermodel.h bundleimporter.cpp bundleimporter.h + materialbrowserbundlemodel.cpp materialbrowserbundlemodel.h + bundlematerial.cpp bundlematerial.h + bundlematerialcategory.cpp bundlematerialcategory.h ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index b9f91d56970..b727df84911 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -312,6 +312,8 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos assignMaterialTo3dModel(modelNode, m_droppedMaterial); }); } + } else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleMaterialDrop) { + emitCustomNotification("drop_bundle_material", {modelNode}); // To MaterialBrowserView } m_nodeAtPosReqType = NodeAtPosReqType::None; } @@ -713,4 +715,10 @@ void Edit3DView::dropMaterial(const ModelNode &matNode, const QPointF &pos) QmlDesignerPlugin::instance()->viewManager().nodeInstanceView()->view3DAction({View3DActionCommand::GetNodeAtPos, pos}); } +void Edit3DView::dropBundleMaterial(const QPointF &pos) +{ + m_nodeAtPosReqType = NodeAtPosReqType::BundleMaterialDrop; + QmlDesignerPlugin::instance()->viewManager().nodeInstanceView()->view3DAction({View3DActionCommand::GetNodeAtPos, pos}); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index 68d217b38ea..cd95b9d371f 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -80,12 +80,14 @@ public: void addQuick3DImport(); void startContextMenu(const QPoint &pos); void dropMaterial(const ModelNode &matNode, const QPointF &pos); + void dropBundleMaterial(const QPointF &pos); private slots: void onEntriesChanged(); private: enum class NodeAtPosReqType { + BundleMaterialDrop, MaterialDrop, ContextMenu, None diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index e439ccf00c3..30b8894bbe9 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -331,7 +331,8 @@ void Edit3DWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent) const DesignerActionManager &actionManager = QmlDesignerPlugin::instance() ->viewManager().designerActionManager(); if (actionManager.externalDragHasSupportedAssets(dragEnterEvent->mimeData()) - || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL)) { + || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL) + || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL)) { dragEnterEvent->acceptProposedAction(); } } @@ -351,6 +352,12 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent) return; } + // handle dropping bundle materials + if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL)) { + m_view->dropBundleMaterial(dropEvent->position()); + return; + } + // handle dropping external assets const DesignerActionManager &actionManager = QmlDesignerPlugin::instance() ->viewManager().designerActionManager(); diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp index 98096337aaf..251ebabe6bb 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp @@ -107,7 +107,7 @@ QString BundleImporter::importComponent(const QString &qmlFile, qmldirContent.append('\n'); } - FilePath qmlSourceFile = FilePath::fromString(qmlFile); + FilePath qmlSourceFile = bundleImportPath.resolvePath(FilePath::fromString(qmlFile)); const bool qmlFileExists = qmlSourceFile.exists(); const QString qmlType = qmlSourceFile.baseName(); m_pendingTypes.append(QStringLiteral("%1.%2") diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.cpp b/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.cpp new file mode 100644 index 00000000000..75549945f41 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.cpp @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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 "bundlematerial.h" + +namespace QmlDesigner { + +BundleMaterial::BundleMaterial(QObject *parent, + const QString &name, + const QString &qml, + const QUrl &icon, + const QStringList &files) + : QObject(parent), m_name(name), m_qml(qml), m_icon(icon), m_files(files) {} + +bool BundleMaterial::filter(const QString &searchText) +{ + if (m_visible != m_name.contains(searchText, Qt::CaseInsensitive)) { + m_visible = !m_visible; + emit materialVisibleChanged(); + } + + return m_visible; +} + +QUrl BundleMaterial::icon() const +{ + return m_icon; +} + +QString BundleMaterial::qml() const +{ + return m_qml; +} + +QStringList BundleMaterial::files() const +{ + return m_files; +} + +bool BundleMaterial::visible() const +{ + return m_visible; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.h b/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.h new file mode 100644 index 00000000000..04f4ae26568 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace QmlDesigner { + +class BundleMaterial : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString bundleMaterialName MEMBER m_name CONSTANT) + Q_PROPERTY(QUrl bundleMaterialIcon MEMBER m_icon CONSTANT) + Q_PROPERTY(bool bundleMaterialVisible MEMBER m_visible NOTIFY materialVisibleChanged) + +public: + BundleMaterial(QObject *parent, + const QString &name, + const QString &qml, + const QUrl &icon, + const QStringList &files); + + bool filter(const QString &searchText); + + QUrl icon() const; + QString qml() const; + QStringList files() const; + bool visible() const; + +signals: + void materialVisibleChanged(); + +private: + QString m_name; + QString m_qml; + QUrl m_icon; + QStringList m_files; + + bool m_visible = true; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.cpp b/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.cpp new file mode 100644 index 00000000000..9a18ff75014 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "bundlematerialcategory.h" + +#include "bundlematerial.h" + +namespace QmlDesigner { + +BundleMaterialCategory::BundleMaterialCategory(QObject *parent, const QString &name) + : QObject(parent), m_name(name) {} + +void BundleMaterialCategory::addBundleMaterial(BundleMaterial *bundleMat) +{ + m_categoryMaterials.append(bundleMat); +} + +bool BundleMaterialCategory::filter(const QString &searchText) +{ + bool visible = false; + for (BundleMaterial *mat : std::as_const(m_categoryMaterials)) + visible |= mat->filter(searchText); + + if (visible != m_visible) { + m_visible = visible; + emit categoryVisibleChanged(); + return true; + } + + return false; +} + +QString BundleMaterialCategory::name() const +{ + return m_name; +} + +bool BundleMaterialCategory::visible() const +{ + return m_visible; +} + +QList BundleMaterialCategory::categoryMaterials() const +{ + return m_categoryMaterials; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.h b/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.h new file mode 100644 index 00000000000..0a3a0f7c028 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** 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 + +namespace QmlDesigner { + +class BundleMaterial; + +class BundleMaterialCategory : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString categoryName MEMBER m_name CONSTANT) + Q_PROPERTY(bool categoryVisible MEMBER m_visible NOTIFY categoryVisibleChanged) + +public: + BundleMaterialCategory(QObject *parent, const QString &name); + + void addBundleMaterial(BundleMaterial *bundleMat); + bool filter(const QString &searchText); + + QString name() const; + bool visible() const; + QList categoryMaterials() const; + +signals: + void categoryVisibleChanged(); + +private: + QString m_name; + bool m_visible = true; + + QList m_categoryMaterials; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp new file mode 100644 index 00000000000..cab5bc869d1 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp @@ -0,0 +1,219 @@ +/**************************************************************************** +** +** 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 "materialbrowserbundlemodel.h" + +#include "bundleimporter.h" +#include "bundlematerial.h" +#include "bundlematerialcategory.h" +#include "utils/qtcassert.h" + +#include +#include +#include + +namespace QmlDesigner { + +MaterialBrowserBundleModel::MaterialBrowserBundleModel(QObject *parent) + : QAbstractListModel(parent) +{ + loadMaterialBundle(); +} + +int MaterialBrowserBundleModel::rowCount(const QModelIndex &) const +{ + return m_bundleCategories.size(); +} + +QVariant MaterialBrowserBundleModel::data(const QModelIndex &index, int role) const +{ + QTC_ASSERT(index.isValid() && index.row() < m_bundleCategories.count(), return {}); + QTC_ASSERT(roleNames().contains(role), return {}); + + QByteArray roleName = roleNames().value(role); + if (roleName == "bundleCategory") + return m_bundleCategories.at(index.row())->name(); + + if (roleName == "bundleCategoryVisible") + return m_bundleCategories.at(index.row())->visible(); + + if (roleName == "bundleMaterialsModel") + return QVariant::fromValue(m_bundleCategories.at(index.row())->categoryMaterials()); + + return {}; +} + +bool MaterialBrowserBundleModel::isValidIndex(int idx) const +{ + return idx > -1 && idx < rowCount(); +} + +QHash MaterialBrowserBundleModel::roleNames() const +{ + static const QHash roles { + {Qt::UserRole + 1, "bundleCategory"}, + {Qt::UserRole + 2, "bundleCategoryVisible"}, + {Qt::UserRole + 3, "bundleMaterialsModel"} + }; + return roles; +} + +void MaterialBrowserBundleModel::loadMaterialBundle() +{ + if (m_matBundleExists) + return; + + QString bundlePath = qEnvironmentVariable("MATERIAL_BUNDLE_PATH"); + + if (bundlePath.isEmpty()) + return; + + QString matBundlePath = bundlePath + "material_bundle.json"; + + if (m_matBundleObj.isEmpty()) { + QFile matPropsFile(matBundlePath); + + if (!matPropsFile.open(QIODevice::ReadOnly)) { + qWarning("Couldn't open material_bundle.json"); + return; + } + + QJsonDocument matBundleJsonDoc = QJsonDocument::fromJson(matPropsFile.readAll()); + if (matBundleJsonDoc.isNull()) { + qWarning("Invalid material_bundle.json file"); + return; + } else { + m_matBundleObj = matBundleJsonDoc.object(); + } + } + + m_matBundleExists = true; + + const QJsonObject catsObj = m_matBundleObj.value("categories").toObject(); + const QStringList categories = catsObj.keys(); + for (const QString &cat : categories) { + auto category = new BundleMaterialCategory(this, cat); + + const QJsonObject matsObj = catsObj.value(cat).toObject(); + const QStringList mats = matsObj.keys(); + for (const QString &mat : mats) { + const QJsonObject matObj = matsObj.value(mat).toObject(); + + QStringList files; + const QJsonArray assetsArr = matObj.value("files").toArray(); + for (const QJsonValueRef &asset : assetsArr) + files.append(asset.toString()); + + auto bundleMat = new BundleMaterial(category, mat, matObj.value("qml").toString(), + QUrl::fromLocalFile(bundlePath + matObj.value("icon").toString()), files); + + category->addBundleMaterial(bundleMat); + } + m_bundleCategories.append(category); + } + + QStringList sharedFiles; + const QJsonArray sharedFilesArr = m_matBundleObj.value("sharedFiles").toArray(); + for (const QJsonValueRef &file : sharedFilesArr) + sharedFiles.append(file.toString()); + + m_importer = new Internal::BundleImporter(bundlePath, "MaterialBundle", sharedFiles); + connect(m_importer, &Internal::BundleImporter::importFinished, this, [&](const QmlDesigner::NodeMetaInfo &metaInfo) { + if (metaInfo.isValid()) + emit addBundleMaterialToProjectRequested(metaInfo); + }); +} + +bool MaterialBrowserBundleModel::hasQuick3DImport() const +{ + return m_hasQuick3DImport; +} + +void MaterialBrowserBundleModel::setHasQuick3DImport(bool b) +{ + if (b == m_hasQuick3DImport) + return; + + m_hasQuick3DImport = b; + emit hasQuick3DImportChanged(); +} + +bool MaterialBrowserBundleModel::hasMaterialRoot() const +{ + return m_hasMaterialRoot; +} + +void MaterialBrowserBundleModel::setHasMaterialRoot(bool b) +{ + if (m_hasMaterialRoot == b) + return; + + m_hasMaterialRoot = b; + emit hasMaterialRootChanged(); +} + +void MaterialBrowserBundleModel::setSearchText(const QString &searchText) +{ + QString lowerSearchText = searchText.toLower(); + + if (m_searchText == lowerSearchText) + return; + + m_searchText = lowerSearchText; + + bool anyCatVisible = false; + bool catVisibilityChanged = false; + + for (BundleMaterialCategory *cat : std::as_const(m_bundleCategories)) { + catVisibilityChanged |= cat->filter(m_searchText); + anyCatVisible |= cat->visible(); + } + + if (anyCatVisible == m_isEmpty) { + m_isEmpty = !anyCatVisible; + emit isEmptyChanged(); + } + + if (catVisibilityChanged) + resetModel(); +} + +void MaterialBrowserBundleModel::resetModel() +{ + beginResetModel(); + endResetModel(); +} + +void MaterialBrowserBundleModel::applyToSelected(BundleMaterial *mat, bool add) +{ + emit applyToSelectedTriggered(mat, add); +} + +void MaterialBrowserBundleModel::addMaterial(BundleMaterial *mat) +{ + m_importer->importComponent(mat->qml(), mat->files()); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h new file mode 100644 index 00000000000..47d201a8293 --- /dev/null +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** 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 + +namespace QmlDesigner { + +class BundleMaterial; +class BundleMaterialCategory; + +namespace Internal { +class BundleImporter; +} + +class MaterialBrowserBundleModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(bool matBundleExists MEMBER m_matBundleExists CONSTANT) + Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) + Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) + Q_PROPERTY(bool hasMaterialRoot READ hasMaterialRoot WRITE setHasMaterialRoot NOTIFY hasMaterialRootChanged) + +public: + MaterialBrowserBundleModel(QObject *parent = nullptr); + + 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); + + bool hasMaterialRoot() const; + void setHasMaterialRoot(bool b); + + void resetModel(); + + Q_INVOKABLE void applyToSelected(QmlDesigner::BundleMaterial *mat, bool add = false); + Q_INVOKABLE void addMaterial(QmlDesigner::BundleMaterial *mat); + +signals: + void isEmptyChanged(); + void hasQuick3DImportChanged(); + void hasMaterialRootChanged(); + void materialVisibleChanged(); + void applyToSelectedTriggered(QmlDesigner::BundleMaterial *mat, bool add = false); + void addBundleMaterialToProjectRequested(const QmlDesigner::NodeMetaInfo &metaInfo); + +private: + void loadMaterialBundle(); + bool isValidIndex(int idx) const; + + QString m_searchText; + QList m_bundleCategories; + QJsonObject m_matBundleObj; + Internal::BundleImporter *m_importer = nullptr; + + bool m_isEmpty = true; + bool m_hasQuick3DImport = false; + bool m_hasMaterialRoot = false; + bool m_matBundleExists = false; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp index 2d61b97889f..fea4a430a61 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp @@ -235,6 +235,8 @@ void MaterialBrowserModel::setSearchText(const QString &searchText) isEmpty = !isValidIndex(m_selectedIndex + inc) && !isValidIndex(m_selectedIndex - inc); } + if (!isMaterialVisible(m_selectedIndex)) // handles the case of a single material + isEmpty = true; } if (isEmpty != m_isEmpty) { diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 1b7ee3f71f9..7a4da256e53 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -26,8 +26,10 @@ #include "materialbrowserview.h" #include "bindingproperty.h" +#include "bundlematerial.h" #include "materialbrowserwidget.h" #include "materialbrowsermodel.h" +#include "materialbrowserbundlemodel.h" #include "nodeabstractproperty.h" #include "nodemetainfo.h" #include "qmlobjectnode.h" @@ -36,9 +38,12 @@ #include #include #include +#include #include #include +#include +#include namespace QmlDesigner { @@ -115,6 +120,80 @@ WidgetInfo MaterialBrowserView::widgetInfo() } }); }); + + connect(m_widget, &MaterialBrowserWidget::bundleMaterialDragStarted, this, + [&] (QmlDesigner::BundleMaterial *bundleMat) { + m_draggedBundleMaterial = bundleMat; + }); + + MaterialBrowserBundleModel *matBrowserBundleModel = m_widget->materialBrowserBundleModel().data(); + + connect(matBrowserBundleModel, &MaterialBrowserBundleModel::applyToSelectedTriggered, this, + [&] (BundleMaterial *material, bool add) { + if (!m_selectedModel.isValid()) + return; + + m_bundleMaterialDropTarget = m_selectedModel; + m_bundleMaterialAddToSelected = add; + m_widget->materialBrowserBundleModel()->addMaterial(material); + }); + + connect(matBrowserBundleModel, &MaterialBrowserBundleModel::addBundleMaterialToProjectRequested, this, + [&] (const QmlDesigner::NodeMetaInfo &metaInfo) { + ModelNode matLib = materialLibraryNode(); + if (!matLib.isValid()) + return; + + executeInTransaction("MaterialBrowserView::widgetInfo", [&] { + ModelNode newMatNode = createModelNode(metaInfo.typeName(), metaInfo.majorVersion(), + metaInfo.minorVersion()); + matLib.defaultNodeListProperty().reparentHere(newMatNode); + + static QRegularExpression rgx("([A-Z])([a-z]*)"); + QString newName = QString::fromLatin1(metaInfo.simplifiedTypeName()).replace(rgx, " \\1\\2").trimmed(); + QString newId = model()->generateIdFromName(newName, "material"); + newMatNode.setIdWithRefactoring(newId); + + VariantProperty objNameProp = newMatNode.variantProperty("objectName"); + objNameProp.setValue(newName); + + if (m_bundleMaterialDropTarget.isValid()) { + QmlObjectNode qmlObjNode(m_bundleMaterialDropTarget); + if (m_bundleMaterialAddToSelected) { + // TODO: unify this logic as it exist elsewhere also + 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(); + }; + QStringList matList = expToList(qmlObjNode.expression("materials")); + matList.append(newMatNode.id()); + QString updatedExp = listToExp(matList); + qmlObjNode.setBindingProperty("materials", updatedExp); + } else { + qmlObjNode.setBindingProperty("materials", newMatNode.id()); + } + m_bundleMaterialDropTarget = {}; + } + + m_bundleMaterialAddToSelected = false; + }); + }); } return createWidgetInfo(m_widget.data(), @@ -185,24 +264,24 @@ void MaterialBrowserView::selectedNodesChanged(const QList &selectedN { Q_UNUSED(lastSelectedNodeList) - ModelNode selectedModel; + m_selectedModel = {}; for (const ModelNode &node : selectedNodeList) { if (node.isSubclassOf("QtQuick3D.Model")) { - selectedModel = node; + m_selectedModel = node; break; } } - m_widget->materialBrowserModel()->setHasModelSelection(selectedModel.isValid()); + m_widget->materialBrowserModel()->setHasModelSelection(m_selectedModel.isValid()); if (!m_autoSelectModelMaterial) return; - if (selectedNodeList.size() > 1 || !selectedModel.isValid()) + if (selectedNodeList.size() > 1 || !m_selectedModel.isValid()) return; - QmlObjectNode qmlObjNode(selectedModel); + QmlObjectNode qmlObjNode(m_selectedModel); QString matExp = qmlObjNode.expression("materials"); if (matExp.isEmpty()) return; @@ -339,6 +418,10 @@ void MaterialBrowserView::customNotification(const AbstractView *view, const QSt }); } else if (identifier == "delete_selected_material") { m_widget->materialBrowserModel()->deleteSelectedMaterial(); + } else if (identifier == "drop_bundle_material") { + m_bundleMaterialDropTarget = nodeList.first(); + m_widget->materialBrowserBundleModel()->addMaterial(m_draggedBundleMaterial); + m_draggedBundleMaterial = nullptr; } } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h index 82ecfe593ed..09f2c99033c 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -31,6 +31,7 @@ namespace QmlDesigner { +class BundleMaterial; class MaterialBrowserWidget; class MaterialBrowserView : public AbstractView @@ -65,13 +66,17 @@ public: private: void refreshModel(bool updateImages); bool isMaterial(const ModelNode &node) const; + void loadPropertyGroups(); QPointer m_widget; + ModelNode m_bundleMaterialDropTarget; + ModelNode m_selectedModel; // first selected 3D model node + BundleMaterial *m_draggedBundleMaterial = nullptr; + bool m_bundleMaterialAddToSelected = false; bool m_hasQuick3DImport = false; bool m_autoSelectModelMaterial = false; // TODO: wire this to some action bool m_puppetResetPending = false; bool m_propertyGroupsLoaded = false; - void loadPropertyGroups(); }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index 71ddd1aca7f..99f063dfae4 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -24,25 +24,29 @@ ****************************************************************************/ #include "materialbrowserwidget.h" + +#include "bundlematerial.h" +#include "materialbrowserbundlemodel.h" #include "materialbrowsermodel.h" #include "materialbrowserview.h" -#include - #include #include #include #include #include -#include -#include -#include +#include + +#include +#include +#include -#include #include #include #include +#include +#include #include #include #include @@ -113,7 +117,7 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) if (m_materialToDrag.isValid()) { QMouseEvent *me = static_cast(event); - if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 10) { + if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 20) { QByteArray data; QMimeData *mimeData = new QMimeData; QDataStream stream(&data, QIODevice::WriteOnly); @@ -125,6 +129,17 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) QString::number(m_materialToDrag.internalId()), nullptr, {128, 128})); m_materialToDrag = {}; } + } else if (m_bundleMaterialToDrag != nullptr) { + QMouseEvent *me = static_cast(event); + if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 20) { + QMimeData *mimeData = new QMimeData; + mimeData->setData(Constants::MIME_TYPE_BUNDLE_MATERIAL, {}); + mimeData->removeFormat("text/plain"); + + model->startDrag(mimeData, m_bundleMaterialToDrag->icon().toLocalFile()); + emit bundleMaterialDragStarted(m_bundleMaterialToDrag); + m_bundleMaterialToDrag = {}; + } } } @@ -134,6 +149,7 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event) MaterialBrowserWidget::MaterialBrowserWidget(MaterialBrowserView *view) : m_materialBrowserView(view) , m_materialBrowserModel(new MaterialBrowserModel(this)) + , m_materialBrowserBundleModel(new MaterialBrowserBundleModel(this)) , m_quickWidget(new QQuickWidget(this)) , m_previewImageProvider(new PreviewImageProvider()) { @@ -152,6 +168,7 @@ MaterialBrowserWidget::MaterialBrowserWidget(MaterialBrowserView *view) m_quickWidget->rootContext()->setContextProperties({ {"rootView", QVariant::fromValue(this)}, {"materialBrowserModel", QVariant::fromValue(m_materialBrowserModel.data())}, + {"materialBrowserBundleModel", QVariant::fromValue(m_materialBrowserBundleModel.data())}, }); m_quickWidget->engine()->addImageProvider("materialBrowser", m_previewImageProvider); @@ -211,6 +228,12 @@ void MaterialBrowserWidget::startDragMaterial(int index, const QPointF &mousePos m_dragStartPoint = mousePos.toPoint(); } +void MaterialBrowserWidget::startDragBundleMaterial(QmlDesigner::BundleMaterial *bundleMat, const QPointF &mousePos) +{ + m_bundleMaterialToDrag = bundleMat; + m_dragStartPoint = mousePos.toPoint(); +} + QString MaterialBrowserWidget::qmlSourcesPath() { #ifdef SHARE_QML_PATH @@ -238,6 +261,7 @@ void MaterialBrowserWidget::reloadQmlSource() void MaterialBrowserWidget::updateSearch() { m_materialBrowserModel->setSearchText(m_filterText); + m_materialBrowserBundleModel->setSearchText(m_filterText); m_quickWidget->update(); } @@ -251,4 +275,10 @@ QPointer MaterialBrowserWidget::materialBrowserModel() con return m_materialBrowserModel; } +QPointer MaterialBrowserWidget::materialBrowserBundleModel() const +{ + return m_materialBrowserBundleModel; +} + + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h index 9c08f21f9af..93197225a11 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -48,8 +49,10 @@ QT_END_NAMESPACE namespace QmlDesigner { +class BundleMaterial; class MaterialBrowserView; class MaterialBrowserModel; +class MaterialBrowserBundleModel; class PreviewImageProvider; class MaterialBrowserWidget : public QFrame @@ -67,13 +70,18 @@ public: void clearSearchFilter(); QPointer materialBrowserModel() const; + QPointer materialBrowserBundleModel() const; void updateMaterialPreview(const ModelNode &node, const QPixmap &pixmap); Q_INVOKABLE void handleSearchfilterChanged(const QString &filterText); Q_INVOKABLE void startDragMaterial(int index, const QPointF &mousePos); + Q_INVOKABLE void startDragBundleMaterial(QmlDesigner::BundleMaterial *bundleMat, const QPointF &mousePos); QQuickWidget *quickWidget() const; +signals: + void bundleMaterialDragStarted(QmlDesigner::BundleMaterial *bundleMat); + protected: bool eventFilter(QObject *obj, QEvent *event) override; @@ -83,6 +91,7 @@ private: QPointer m_materialBrowserView; QPointer m_materialBrowserModel; + QPointer m_materialBrowserBundleModel; QScopedPointer m_quickWidget; QShortcut *m_qmlSourceUpdateShortcut = nullptr; @@ -92,6 +101,7 @@ private: QString m_filterText; ModelNode m_materialToDrag; + BundleMaterial *m_bundleMaterialToDrag = nullptr; QPoint m_dragStartPoint; }; diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index 5d522dfbaf0..b042888cfb1 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -96,6 +96,7 @@ const char MATERIAL_LIB_ID[] = "__materialLibrary__"; const char MIME_TYPE_ITEM_LIBRARY_INFO[] = "application/vnd.qtdesignstudio.itemlibraryinfo"; const char MIME_TYPE_ASSETS[] = "application/vnd.qtdesignstudio.assets"; const char MIME_TYPE_MATERIAL[] = "application/vnd.qtdesignstudio.material"; +const char MIME_TYPE_BUNDLE_MATERIAL[] = "application/vnd.qtdesignstudio.bundlematerial"; const char MIME_TYPE_ASSET_IMAGE[] = "application/vnd.qtdesignstudio.asset.image"; const char MIME_TYPE_ASSET_FONT[] = "application/vnd.qtdesignstudio.asset.font"; const char MIME_TYPE_ASSET_SHADER[] = "application/vnd.qtdesignstudio.asset.shader"; diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 1744520f5b0..51cd8b970aa 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -687,6 +687,12 @@ Project { "itemlibrary/itemlibraryiconimageprovider.h", "materialbrowser/materialbrowsermodel.cpp", "materialbrowser/materialbrowsermodel.h", + "materialbrowser/materialbrowserbundlemodel.cpp", + "materialbrowser/materialbrowserbundlemodel.h", + "materialbrowser/bundlematerial.cpp", + "materialbrowser/bundlematerial.h", + "materialbrowser/bundlematerialcategory.cpp", + "materialbrowser/bundlematerialcategory.h", "materialbrowser/materialbrowserview.cpp", "materialbrowser/materialbrowserview.h", "materialbrowser/materialbrowserwidget.cpp",