diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowserContextMenu.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowserContextMenu.qml index 248e07bf6c4..e211b18b221 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowserContextMenu.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowserContextMenu.qml @@ -134,4 +134,13 @@ StudioControls.Menu { onTriggered: MaterialBrowserBackend.rootView.addMaterialToContentLibrary() } + + // TODO: implement + // StudioControls.MenuItem { + // text: qsTr("Export material") + // enabled: !materialBrowserModel.selectedMaterialIsComponent // TODO: support component materials + // visible: false + + // onTriggered: MaterialBrowserBackend.rootView.exportMaterial() + // } } diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 8180a707824..a686a4efd8c 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -492,7 +492,7 @@ add_qtc_plugin(QmlDesigner PLUGIN_MANUAL_DEPENDS LicenseChecker ${IDE_VERSION} optional DEPENDS QmlJS LanguageUtils QmlEditorWidgets AdvancedDockingSystem - Qt::QuickWidgets Qt::CorePrivate Qt::Xml Qt::Svg QmlDesignerCore Sqlite + Qt::QuickWidgets Qt::CorePrivate Qt::Xml Qt::Svg QmlDesignerCore Sqlite Zip PUBLIC_DEPENDS QmlDesignerUtils QmlPuppetCommunication QmlDesignerBase DEFINES diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp index 21723314a4b..f6fa0d0a269 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp @@ -308,29 +308,31 @@ QPair ContentLibraryUserModel::getUniqueLib3DNames(const QStri QPair ContentLibraryUserModel::getUniqueLibItemNames(const QString &defaultName, const QJsonObject &bundleObj) const { - QTC_ASSERT(!bundleObj.isEmpty(), return {}); + QString uniqueQml = UniqueName::generateId(defaultName); + uniqueQml[0] = uniqueQml.at(0).toUpper(); + uniqueQml.prepend("My"); - const QJsonArray itemsArr = bundleObj.value("items").toArray(); + QString uniqueIcon = defaultName; - QStringList itemQmls, itemIcons; - for (const QJsonValueConstRef &itemRef : itemsArr) { - const QJsonObject &obj = itemRef.toObject(); - itemQmls.append(obj.value("qml").toString().chopped(4)); // remove .qml - itemIcons.append(QFileInfo(obj.value("icon").toString()).baseName()); + if (!bundleObj.isEmpty()) { + const QJsonArray itemsArr = bundleObj.value("items").toArray(); + + QStringList itemQmls, itemIcons; + for (const QJsonValueConstRef &itemRef : itemsArr) { + const QJsonObject &obj = itemRef.toObject(); + itemQmls.append(obj.value("qml").toString().chopped(4)); // remove .qml + itemIcons.append(QFileInfo(obj.value("icon").toString()).baseName()); + } + + uniqueQml = UniqueName::generate(uniqueQml, [&] (const QString &name) { + return itemQmls.contains(name); + }); + + uniqueIcon = UniqueName::generate(uniqueIcon, [&] (const QString &name) { + return itemIcons.contains(name); + }); } - QString baseQml = UniqueName::generateId(defaultName); - baseQml[0] = baseQml.at(0).toUpper(); - baseQml.prepend("My"); - - QString uniqueQml = UniqueName::generate(baseQml, [&] (const QString &name) { - return itemQmls.contains(name); - }); - - QString uniqueIcon = UniqueName::generate(defaultName, [&] (const QString &name) { - return itemIcons.contains(name); - }); - return {uniqueQml + ".qml", uniqueIcon + ".png"}; } diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h index 1ace815b2cc..b292ebdc9c4 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h @@ -43,6 +43,8 @@ public: QPair getUniqueLibMaterialNames(const QString &defaultName = "Material") const; QPair getUniqueLib3DNames(const QString &defaultName = "Item") const; + QPair getUniqueLibItemNames(const QString &defaultName = "Item", + const QJsonObject &bundleObj = {}) const; void setQuick3DImportVersion(int major, int minor); @@ -97,8 +99,6 @@ private: void loadTextureBundle(); void removeMaterialFromContentLib(ContentLibraryMaterial *mat); void remove3DFromContentLib(ContentLibraryItem *item); - QPair getUniqueLibItemNames(const QString &defaultName, - const QJsonObject &bundleObj) const; ContentLibraryWidget *m_widget = nullptr; QString m_searchText; diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index 1522db75337..4873a8732f0 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -27,6 +27,8 @@ #include #include +#include + #include #ifndef QMLDESIGNER_TEST @@ -37,11 +39,14 @@ #include #endif +#include +#include #include #include #include #include #include +#include #include namespace QmlDesigner { @@ -373,6 +378,10 @@ void ContentLibraryView::customNotification(const AbstractView *view, addLib3DComponent(nodeList.first()); else addLib3DItem(nodeList.first()); + } else if (identifier == "export_item_as_bundle") { + exportLib3DItem(nodeList.first()); + } else if (identifier == "export_material_as_bundle") { + // TODO } } @@ -510,7 +519,7 @@ void ContentLibraryView::addLibMaterial(const ModelNode &node, const QPixmap &ic auto [qml, icon] = m_widget->userModel()->getUniqueLibMaterialNames(node.id()); QString iconPath = QLatin1String("icons/%1").arg(icon); - QString fullIconPath = bundlePath.pathAppended(iconPath).toString(); + QString fullIconPath = bundlePath.pathAppended(iconPath).toFSPathString(); // save icon bool iconSaved = iconPixmap.save(fullIconPath); @@ -518,66 +527,53 @@ void ContentLibraryView::addLibMaterial(const ModelNode &node, const QPixmap &ic qWarning() << __FUNCTION__ << "icon save failed"; // generate and save material Qml file - const QStringList depAssets = writeLibItemQml(node, qml); + auto [qmlString, depAssets] = modelNodeToQmlString(node); + const QStringList depAssetsList = depAssets.values(); + + auto result = bundlePath.pathAppended(qml).writeFileContents(qmlString.toUtf8()); + QTC_ASSERT_EXPECTED(result,); // add the material to the bundle json QJsonObject &jsonRef = m_widget->userModel()->bundleJsonMaterialObjectRef(); QJsonArray itemsArr = jsonRef.value("items").toArray(); - QJsonObject itemObj; - itemObj.insert("name", name); - itemObj.insert("qml", qml); - itemObj.insert("icon", iconPath); - QJsonArray filesArr; - for (const QString &assetPath : depAssets) - filesArr.append(assetPath); - itemObj.insert("files", filesArr); + itemsArr.append(QJsonObject { + {"name", name}, + {"qml", qml}, + {"icon", iconPath}, + {"files", QJsonArray::fromStringList(depAssetsList)} + }); - itemsArr.append(itemObj); jsonRef["items"] = itemsArr; - auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME) + result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME) .writeFileContents(QJsonDocument(jsonRef).toJson()); - if (!result) - qWarning() << __FUNCTION__ << result.error(); + QTC_ASSERT_EXPECTED(result,); // copy material assets to bundle folder - for (const QString &assetPath : depAssets) { + for (const QString &assetPath : depAssetsList) { Utils::FilePath assetPathSource = DocumentManager::currentResourcePath().pathAppended(assetPath); Utils::FilePath assetPathTarget = bundlePath.pathAppended(assetPath); assetPathTarget.parentDir().ensureWritableDir(); auto result = assetPathSource.copyFile(assetPathTarget); - if (!result) - qWarning() << __FUNCTION__ << result.error(); + QTC_ASSERT_EXPECTED(result,); } - m_widget->userModel()->addMaterial(name, qml, QUrl::fromLocalFile(fullIconPath), depAssets); + m_widget->userModel()->addMaterial(name, qml, QUrl::fromLocalFile(fullIconPath), depAssetsList); } -QStringList ContentLibraryView::writeLibItemQml(const ModelNode &node, const QString &qml) +QPair> ContentLibraryView::modelNodeToQmlString(const ModelNode &node, int depth) { - QStringList depListIds; - auto [qmlString, assets] = modelNodeToQmlString(node, depListIds); + static QStringList depListIds; - qmlString.prepend("import QtQuick\nimport QtQuick3D\n\n"); - - QString itemType = QLatin1String(node.metaInfo().isQtQuick3DMaterial() ? "materials" : "3d"); - auto qmlPath = Utils::FilePath::fromString(QLatin1String("%1/User/%2/%3") - .arg(Paths::bundlesPathSetting(), itemType, qml)); - auto result = qmlPath.writeFileContents(qmlString.toUtf8()); - if (!result) - qWarning() << __FUNCTION__ << result.error(); - - return assets.values(); -} - -QPair> ContentLibraryView::modelNodeToQmlString(const ModelNode &node, - QStringList &depListIds, - int depth) -{ QString qml; QSet assets; + if (depth == 0) { + qml.append("import QtQuick\nimport QtQuick3D\n\n"); + depListIds.clear(); + } + QString indent = QString(" ").repeated(depth * 4); qml += indent + node.simplifiedTypeName() + " {\n"; @@ -629,7 +625,7 @@ QPair> ContentLibraryView::modelNodeToQmlString(const Mod if (depNode && !depListIds.contains(depNode.id())) { depListIds.append(depNode.id()); - auto [depQml, depAssets] = modelNodeToQmlString(depNode, depListIds, depth + 1); + auto [depQml, depAssets] = modelNodeToQmlString(depNode, depth + 1); qml += "\n" + depQml + "\n"; assets.unite(depAssets); } @@ -662,7 +658,7 @@ void ContentLibraryView::addLibAssets(const QStringList &paths) // save icon QString iconSavePath = bundlePath.pathAppended("icons/" + assetFilePath.baseName() + ".png") - .toString(); + .toFSPathString(); QPixmap icon = asset.pixmap({120, 120}); bool iconSaved = icon.save(iconSavePath); if (!iconSaved) @@ -670,10 +666,9 @@ void ContentLibraryView::addLibAssets(const QStringList &paths) // save asset auto result = assetFilePath.copyFile(bundlePath.pathAppended(asset.fileName())); - if (!result) - qWarning() << __FUNCTION__ << result.error(); + QTC_ASSERT_EXPECTED(result,); - pathsInBundle.append(bundlePath.pathAppended(asset.fileName()).toString()); + pathsInBundle.append(bundlePath.pathAppended(asset.fileName()).toFSPathString()); } m_widget->userModel()->addTextures(pathsInBundle); @@ -706,8 +701,15 @@ void ContentLibraryView::addLib3DComponent(const ModelNode &node) // generate and save icon QString iconPath = QLatin1String("icons/%1").arg(UniqueName::generateId(compBaseName) + ".png"); - QString fullIconPath = bundlePath.pathAppended(iconPath).toString(); - genAndSaveIcon(compDir.pathAppended(compFileName).path(), fullIconPath); + m_iconSavePath = bundlePath.pathAppended(iconPath); + m_iconSavePath.parentDir().ensureWritableDir(); + getImageFromCache(compDir.pathAppended(compFileName).path(), [&](const QImage &image) { + bool iconSaved = image.save(m_iconSavePath.toFSPathString()); + if (iconSaved) + m_widget->userModel()->refresh3DSection(); + else + qWarning() << "ContentLibraryView::getImageFromCache(): icon save failed" << iconPath; + }); const Utils::FilePaths sourceFiles = compDir.dirEntries({{}, QDir::Files, QDirIterator::Subdirectories}); const QStringList ignoreList {"_importdata.json", "qmldir", compBaseName + ".hints"}; @@ -723,8 +725,7 @@ void ContentLibraryView::addLib3DComponent(const ModelNode &node) // copy item from project to user bundle auto result = sourcePath.copyFile(targetPath); - if (!result) - qWarning() << __FUNCTION__ << result.error(); + QTC_ASSERT_EXPECTED(result,); if (sourcePath.fileName() != compFileName) // skip component file (only collect dependencies) filesList.append(relativePath.path()); @@ -744,11 +745,9 @@ void ContentLibraryView::addLib3DComponent(const ModelNode &node) auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME) .writeFileContents(QJsonDocument(jsonRef).toJson()); - if (!result) - qWarning() << __FUNCTION__ << result.error(); + QTC_ASSERT_EXPECTED(result,); - m_widget->userModel()->add3DItem(compBaseName, compFileName, QUrl::fromLocalFile(fullIconPath), - filesList); + m_widget->userModel()->add3DItem(compBaseName, compFileName, m_iconSavePath.toUrl(), filesList); } void ContentLibraryView::addLib3DItem(const ModelNode &node) @@ -756,19 +755,31 @@ void ContentLibraryView::addLib3DItem(const ModelNode &node) auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/3d/"); QString name = node.variantProperty("objectName").value().toString(); - auto [qml, icon] = m_widget->userModel()->getUniqueLib3DNames(node.id()); - QString iconPath = QLatin1String("icons/%1").arg(icon); - if (name.isEmpty()) name = node.id(); - // generate and save item Qml file - const QStringList depAssets = writeLibItemQml(node, qml); + auto [qml, icon] = m_widget->userModel()->getUniqueLibItemNames(node.id(), + m_widget->userModel()->bundleJson3DObjectRef()); + + // generate and save Qml file + auto [qmlString, depAssets] = modelNodeToQmlString(node); + const QStringList depAssetsList = depAssets.values(); + + auto result = bundlePath.pathAppended(qml).writeFileContents(qmlString.toUtf8()); + QTC_ASSERT_EXPECTED(result,); // generate and save icon - QString qmlPath = QLatin1String("%1/User/3d/%2").arg(Paths::bundlesPathSetting(), qml); - QString fullIconPath = bundlePath.pathAppended(iconPath).toString(); - genAndSaveIcon(qmlPath, fullIconPath); + QString qmlPath = bundlePath.pathAppended(qml).toFSPathString(); + QString iconPath = QLatin1String("icons/%1").arg(icon); + m_iconSavePath = bundlePath.pathAppended(iconPath); + m_iconSavePath.parentDir().ensureWritableDir(); + getImageFromCache(qmlPath, [&](const QImage &image) { + bool iconSaved = image.save(m_iconSavePath.toFSPathString()); + if (iconSaved) + m_widget->userModel()->refresh3DSection(); + else + qWarning() << "ContentLibraryView::getImageFromCache(): icon save failed" << iconPath; + }); // add the item to the bundle json QJsonObject &jsonRef = m_widget->userModel()->bundleJson3DObjectRef(); @@ -777,28 +788,100 @@ void ContentLibraryView::addLib3DItem(const ModelNode &node) {"name", name}, {"qml", qml}, {"icon", iconPath}, - {"files", QJsonArray::fromStringList(depAssets)} + {"files", QJsonArray::fromStringList(depAssetsList)} }); jsonRef["items"] = itemsArr; - auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME) + result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME) .writeFileContents(QJsonDocument(jsonRef).toJson()); - if (!result) - qWarning() << __FUNCTION__ << result.error(); + QTC_ASSERT_EXPECTED(result,); - // copy item's assets to bundle folder - for (const QString &assetPath : depAssets) { + // copy item's assets to target folder + for (const QString &assetPath : depAssetsList) { Utils::FilePath assetPathSource = DocumentManager::currentResourcePath().pathAppended(assetPath); Utils::FilePath assetPathTarget = bundlePath.pathAppended(assetPath); assetPathTarget.parentDir().ensureWritableDir(); auto result = assetPathSource.copyFile(assetPathTarget); - if (!result) - qWarning() << __FUNCTION__ << result.error(); + QTC_ASSERT_EXPECTED(result,); } - m_widget->userModel()->add3DItem(name, qml, QUrl::fromLocalFile(fullIconPath), depAssets); + m_widget->userModel()->add3DItem(name, qml, m_iconSavePath.toUrl(), depAssetsList); +} + +void ContentLibraryView::exportLib3DItem(const ModelNode &node) +{ + // prompt and get the exported bundle path + QString defaultExportFileName = QLatin1String("%1.%2").arg(node.id(), Constants::BUNDLE_SUFFIX); + Utils::FilePath projectFP = DocumentManager::currentProjectDirPath(); + if (projectFP.isEmpty()) { + projectFP = QmlDesignerPlugin::instance()->documentManager() + .currentDesignDocument()->fileName().parentDir(); + } + + QString exportPath = QFileDialog::getSaveFileName(m_widget, tr("Export Component"), + projectFP.pathAppended(defaultExportFileName).toFSPathString(), + tr("Qt Design Studio Bundle Files (*.%1)").arg(Constants::BUNDLE_SUFFIX)); + if (exportPath.isEmpty()) + return; + + // targetPath is a temp path for collectiong and zipping assets, actual export target is where + // the user chose to export (i.e. exportPath) + QTemporaryDir tempDir; + QTC_ASSERT(tempDir.isValid(), return); + auto targetPath = Utils::FilePath::fromString(tempDir.path()); + + m_zipWriter = std::make_unique(exportPath); + + QString name = node.variantProperty("objectName").value().toString(); + if (name.isEmpty()) + name = node.id(); + + auto [qml, icon] = m_widget->userModel()->getUniqueLibItemNames(node.id()); + + // generate and save Qml file + auto [qmlString, depAssets] = modelNodeToQmlString(node); + const QStringList depAssetsList = depAssets.values(); + + auto qmlFilePath = targetPath.pathAppended(qml); + auto result = qmlFilePath.writeFileContents(qmlString.toUtf8()); + QTC_ASSERT_EXPECTED(result, return); + m_zipWriter->addFile(qmlFilePath.fileName(), qmlString.toUtf8()); + + // generate and save icon + QString iconPath = QLatin1String("icons/%1").arg(icon); + m_iconSavePath = targetPath.pathAppended(iconPath); + getImageFromCache(qmlFilePath.toFSPathString(), [&](const QImage &image) { + QByteArray iconByteArray; + QBuffer buffer(&iconByteArray); + buffer.open(QIODevice::WriteOnly); + image.save(&buffer, "PNG"); + + m_zipWriter->addFile("icons/" + m_iconSavePath.fileName(), iconByteArray); + m_zipWriter->close(); + }); + + // add the item to the bundle json + QJsonObject jsonObj; + QJsonArray itemsArr; + itemsArr.append(QJsonObject { + {"name", name}, + {"qml", qml}, + {"icon", iconPath}, + {"files", QJsonArray::fromStringList(depAssetsList)} + }); + + jsonObj["items"] = itemsArr; + + Utils::FilePath jsonFilePath = targetPath.pathAppended(Constants::BUNDLE_JSON_FILENAME); + m_zipWriter->addFile(jsonFilePath.fileName(), QJsonDocument(jsonObj).toJson()); + + // add item's dependency assets to the bundle zip + for (const QString &assetPath : depAssetsList) { + Utils::FilePath assetPathSource = DocumentManager::currentResourcePath().pathAppended(assetPath); + m_zipWriter->addFile(assetPath, assetPathSource.fileContents().value_or("")); + } } /** @@ -806,26 +889,21 @@ void ContentLibraryView::addLib3DItem(const ModelNode &node) * @param qmlPath path to the qml component file to be rendered * @param iconPath output save path of the generated icon */ -void ContentLibraryView::genAndSaveIcon(const QString &qmlPath, const QString &iconPath) +void ContentLibraryView::getImageFromCache(const QString &qmlPath, + std::function successCallback) { m_imageCache.requestSmallImage( Utils::PathString{qmlPath}, - [&, qmlPath, iconPath](const QImage &image) { - bool iconSaved = image.save(iconPath); - if (iconSaved) - m_widget->userModel()->refresh3DSection(); - else - qWarning() << "ContentLibraryView::genAndSaveIcon(): icon save failed"; - }, + successCallback, [&](ImageCache::AbortReason abortReason) { if (abortReason == ImageCache::AbortReason::Abort) { - qWarning() << QLatin1String("ContentLibraryView::genAndSaveIcon(): icon generation " + qWarning() << QLatin1String("ContentLibraryView::getImageFromCache(): icon generation " "failed for path %1, reason: Abort").arg(qmlPath); } else if (abortReason == ImageCache::AbortReason::Failed) { - qWarning() << QLatin1String("ContentLibraryView::genAndSaveIcon(): icon generation " + qWarning() << QLatin1String("ContentLibraryView::getImageFromCache(): icon generation " "failed for path %1, reason: Failed").arg(qmlPath); } else if (abortReason == ImageCache::AbortReason::NoEntry) { - qWarning() << QLatin1String("ContentLibraryView::genAndSaveIcon(): icon generation " + qWarning() << QLatin1String("ContentLibraryView::getImageFromCache(): icon generation " "failed for path %1, reason: NoEntry").arg(qmlPath); } }); diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h index 914a8b8ea00..a0602ea9be7 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h @@ -8,10 +8,14 @@ #include #include +#include + #include #include +QT_FORWARD_DECLARE_CLASS(QImage) QT_FORWARD_DECLARE_CLASS(QPixmap) +QT_FORWARD_DECLARE_CLASS(ZipWriter) namespace QmlDesigner { @@ -59,10 +63,10 @@ private: void addLibAssets(const QStringList &paths); void addLib3DComponent(const ModelNode &node); void addLib3DItem(const ModelNode &node); - void genAndSaveIcon(const QString &qmlPath, const QString &iconPath); - QStringList writeLibItemQml(const ModelNode &node, const QString &qml); - QPair> modelNodeToQmlString(const ModelNode &node, QStringList &depListIds, - int depth = 0); + void exportLib3DItem(const ModelNode &node); + void getImageFromCache(const QString &qmlPath, + std::function successCallback); + QPair> modelNodeToQmlString(const ModelNode &node, int depth = 0); #ifdef QDS_USE_PROJECTSTORAGE void applyBundleMaterialToDropTarget(const ModelNode &bundleMat, const TypeName &typeName = {}); @@ -89,6 +93,8 @@ private: bool m_hasQuick3DImport = false; qint32 m_sceneId = -1; CreateTexture m_createTexture; + Utils::FilePath m_iconSavePath; + std::unique_ptr m_zipWriter; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 02271518be9..5032cce8e77 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -367,7 +367,19 @@ void Edit3DWidget::createContextMenu() m_addToContentLibAction = m_contextMenu->addAction( contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon tr("Add to Content Library"), [&] { - view()->emitCustomNotification("add_3d_to_content_lib", {m_contextMenuTarget}); + view()->emitCustomNotification("add_3d_to_content_lib", {m_contextMenuTarget}); // To ContentLibrary + }); + + m_importBundleAction = m_contextMenu->addAction( + contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon + tr("Import components"), [&] { + // TODO: implement importing components + }); + + m_exportBundleAction = m_contextMenu->addAction( + contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon + tr("Export components"), [&] { + view()->emitCustomNotification("export_item_as_bundle", {m_contextMenuTarget}); // To ContentLibrary }); m_contextMenu->addSeparator(); @@ -650,6 +662,8 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode m_bakeLightsAction->setVisible(view()->bakeLightsAction()->action()->isVisible()); m_bakeLightsAction->setEnabled(view()->bakeLightsAction()->action()->isEnabled()); m_addToContentLibAction->setEnabled(isNode && !isInBundle); + m_importBundleAction->setEnabled(isNode); + m_exportBundleAction->setEnabled(isNode); if (m_view) { int idx = m_view->activeSplit(); diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h index 97c0469668f..4f0dfa19252 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h @@ -100,6 +100,8 @@ private: QPointer m_selectParentAction; QPointer m_toggleGroupAction; QPointer m_wireFrameAction; + QPointer m_importBundleAction; + QPointer m_exportBundleAction; QPointer m_addToContentLibAction; QHash> m_matOverrideActions; QPointer m_createSubMenu; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index 8723611be07..4de8cea7893 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -368,7 +368,14 @@ void MaterialBrowserWidget::addMaterialToContentLibrary() { ModelNode mat = m_materialBrowserModel->selectedMaterial(); m_materialBrowserView->emitCustomNotification("add_material_to_content_lib", {mat}, - {m_previewImageProvider->getPixmap(mat)}); + {m_previewImageProvider->getPixmap(mat)}); // to ContentLibrary +} + +void MaterialBrowserWidget::exportMaterial() +{ + ModelNode mat = m_materialBrowserModel->selectedMaterial(); + m_materialBrowserView->emitCustomNotification("export_material_as_bundle", {mat}, + {m_previewImageProvider->getPixmap(mat)}); // to ContentLibrary } QString MaterialBrowserWidget::qmlSourcesPath() diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h index 6506283f858..d319fc562e8 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h @@ -63,6 +63,7 @@ public: Q_INVOKABLE void acceptTextureDropOnMaterial(int matIndex, const QString &texId); Q_INVOKABLE void focusMaterialSection(bool focusMatSec); Q_INVOKABLE void addMaterialToContentLibrary(); + Q_INVOKABLE void exportMaterial(); StudioQuickWidget *quickWidget() const; diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index 28f3ed6097b..04b2320e838 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -79,6 +79,7 @@ inline constexpr char EDIT3D_CAMERA_SPEED_CONFIG[] = "QmlDesigner.Editor3D.Camer inline constexpr char QML_DESIGNER_SUBFOLDER[] = "/designer/"; inline constexpr char BUNDLE_JSON_FILENAME[] = "bundle.json"; +inline constexpr char BUNDLE_SUFFIX[] = "qdsbundle"; inline constexpr char COMPONENT_BUNDLES_TYPE[] = "Bundles"; inline constexpr char COMPONENT_BUNDLES_MATERIAL_BUNDLE_TYPE[] = "Materials"; inline constexpr char COMPONENT_BUNDLES_EFFECT_BUNDLE_TYPE[] = "Effects";