aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMahmoud Badri <mahmoud.badri@qt.io>2024-03-21 12:47:09 +0200
committerMahmoud Badri <mahmoud.badri@qt.io>2024-04-08 12:13:16 +0000
commitbc5628afca0c0716642cd69679d6b51acfa60316 (patch)
treeff0ceee4cea89c86f2ef60ad352b68da8e822a74
parentda21fa4c3396e867537909b68f4a053114d195c0 (diff)
QmlDesigner: Add content library user materials bundle
Fixes: QDS-12389 Change-Id: Icec1b06c57e0eaa4ff444e3143d3cba0803c8dd1 Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
-rw-r--r--share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml46
-rw-r--r--share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml13
-rw-r--r--share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialContextMenu.qml13
-rw-r--r--share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml25
-rw-r--r--share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml3
-rw-r--r--share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml151
-rw-r--r--share/qtcreator/qmldesigner/contentLibraryQmlSource/UnimportBundleMaterialDialog.qml23
-rw-r--r--src/plugins/qmldesigner/CMakeLists.txt1
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.h3
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp308
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h118
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp81
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h2
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp17
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h4
15 files changed, 761 insertions, 47 deletions
diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml
index c6db8425ff..2c98b58adc 100644
--- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml
+++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml
@@ -23,6 +23,7 @@ Item {
texturesView.closeContextMenu()
environmentsView.closeContextMenu()
effectsView.closeContextMenu()
+ userView.closeContextMenu()
HelperWidgets.Controller.closeContextMenu()
}
@@ -113,10 +114,18 @@ Item {
id: tabBar
width: parent.width
height: StudioTheme.Values.toolbarHeight
- tabsModel: [{name: qsTr("Materials"), icon: StudioTheme.Constants.material_medium},
- {name: qsTr("Textures"), icon: StudioTheme.Constants.textures_medium},
- {name: qsTr("Environments"), icon: StudioTheme.Constants.languageList_medium},
- {name: qsTr("Effects"), icon: StudioTheme.Constants.effects}]
+
+ Component.onCompleted: {
+ var tabs = [
+ { name: qsTr("Materials"), icon: StudioTheme.Constants.material_medium },
+ { name: qsTr("Textures"), icon: StudioTheme.Constants.textures_medium },
+ { name: qsTr("Environments"), icon: StudioTheme.Constants.languageList_medium },
+ { name: qsTr("Effects"), icon: StudioTheme.Constants.effects }
+ ];
+ if (ContentLibraryBackend.rootView.userBundleEnabled())
+ tabs.push({ name: qsTr("User Assets"), icon: StudioTheme.Constants.effects });
+ tabBar.tabsModel = tabs;
+ }
}
}
}
@@ -148,7 +157,8 @@ Item {
onUnimport: (bundleMat) => {
confirmUnimportDialog.targetBundleItem = bundleMat
- confirmUnimportDialog.targetBundleType = "material"
+ confirmUnimportDialog.targetBundleLabel = "material"
+ confirmUnimportDialog.targetBundleModel = ContentLibraryBackend.materialsModel
confirmUnimportDialog.open()
}
@@ -208,7 +218,31 @@ Item {
onUnimport: (bundleItem) => {
confirmUnimportDialog.targetBundleItem = bundleItem
- confirmUnimportDialog.targetBundleType = "effect"
+ confirmUnimportDialog.targetBundleLabel = "effect"
+ confirmUnimportDialog.targetBundleModel = ContentLibraryBackend.effectsModel
+ confirmUnimportDialog.open()
+ }
+
+ onCountChanged: root.responsiveResize(stackLayout.width, stackLayout.height)
+ }
+
+ ContentLibraryUserView {
+ id: userView
+
+ adsFocus: root.adsFocus
+ width: root.width
+
+ cellWidth: root.thumbnailSize
+ cellHeight: root.thumbnailSize + 20
+ numColumns: root.numColumns
+ hideHorizontalScrollBar: true
+
+ searchBox: searchBox
+
+ onUnimport: (bundleItem) => {
+ confirmUnimportDialog.targetBundleItem = bundleItem
+ confirmUnimportDialog.targetBundleLabel = "material"
+ confirmUnimportDialog.targetBundleModel = ContentLibraryBackend.userModel
confirmUnimportDialog.open()
}
diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml
index 93b226d6ca..0e9fc4903e 100644
--- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml
+++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterial.qml
@@ -12,12 +12,15 @@ import WebFetcher
Item {
id: root
- signal showContextMenu()
-
// Download states: "" (ie default, not downloaded), "unavailable", "downloading", "downloaded",
// "failed"
property string downloadState: modelData.isDownloaded() ? "downloaded" : ""
+ property bool importerRunning: false
+
+ signal showContextMenu()
+ signal addToProject()
+
visible: modelData.bundleMaterialVisible
MouseArea {
@@ -29,7 +32,7 @@ Item {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: (mouse) => {
- if (mouse.button === Qt.LeftButton && !materialsModel.importerRunning) {
+ if (mouse.button === Qt.LeftButton && !root.importerRunning) {
if (root.downloadState === "downloaded")
ContentLibraryBackend.rootView.startDragMaterial(modelData, mapToGlobal(mouse.x, mouse.y))
} else if (mouse.button === Qt.RightButton && root.downloadState === "downloaded") {
@@ -96,12 +99,12 @@ Item {
pressColor: Qt.hsla(c.hslHue, c.hslSaturation, c.hslLightness, .4)
anchors.right: img.right
anchors.bottom: img.bottom
- enabled: !ContentLibraryBackend.materialsModel.importerRunning
+ enabled: !root.importerRunning
visible: root.downloadState === "downloaded"
&& (containsMouse || mouseArea.containsMouse)
onClicked: {
- ContentLibraryBackend.materialsModel.addToProject(modelData)
+ root.addToProject()
}
}
diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialContextMenu.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialContextMenu.qml
index ca3a05bdd1..b67ec311ef 100644
--- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialContextMenu.qml
+++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialContextMenu.qml
@@ -15,8 +15,9 @@ StudioControls.Menu {
readonly property bool targetAvailable: targetMaterial && !importerRunning
- signal unimport(var bundleMat);
- signal addToProject(var bundleMat)
+ signal unimport();
+ signal addToProject()
+ signal applyToSelected(bool add)
function popupMenu(targetMaterial = null)
{
@@ -29,13 +30,13 @@ StudioControls.Menu {
StudioControls.MenuItem {
text: qsTr("Apply to selected (replace)")
enabled: root.targetAvailable && root.hasModelSelection
- onTriggered: materialsModel.applyToSelected(root.targetMaterial, false)
+ onTriggered: root.applyToSelected(false)
}
StudioControls.MenuItem {
text: qsTr("Apply to selected (add)")
enabled: root.targetAvailable && root.hasModelSelection
- onTriggered: materialsModel.applyToSelected(root.targetMaterial, true)
+ onTriggered: root.applyToSelected(true)
}
StudioControls.MenuSeparator {}
@@ -45,7 +46,7 @@ StudioControls.Menu {
text: qsTr("Add an instance to project")
onTriggered: {
- root.addToProject(root.targetMaterial)
+ root.addToProject()
}
}
@@ -53,6 +54,6 @@ StudioControls.Menu {
enabled: root.targetAvailable && root.targetMaterial.bundleMaterialImported
text: qsTr("Remove from project")
- onTriggered: root.unimport(root.targetMaterial)
+ onTriggered: root.unimport()
}
}
diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml
index c21baf4c58..9a0e33b8e5 100644
--- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml
+++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryMaterialsView.qml
@@ -27,8 +27,6 @@ HelperWidgets.ScrollView {
root.count = c
}
- property var currMaterialItem: null
- property var rootItem: null
property var materialsModel: ContentLibraryBackend.materialsModel
required property var searchBox
@@ -51,17 +49,19 @@ HelperWidgets.ScrollView {
ContentLibraryMaterialContextMenu {
id: ctxMenu
- hasModelSelection: materialsModel.hasModelSelection
- importerRunning: materialsModel.importerRunning
+ hasModelSelection: root.materialsModel.hasModelSelection
+ importerRunning: root.materialsModel.importerRunning
- onUnimport: (bundleMat) => root.unimport(bundleMat)
- onAddToProject: (bundleMat) => materialsModel.addToProject(bundleMat)
+ onApplyToSelected: (add) => root.materialsModel.applyToSelected(ctxMenu.targetMaterial, add)
+
+ onUnimport: root.unimport(ctxMenu.targetMaterial)
+ onAddToProject: root.materialsModel.addToProject(ctxMenu.targetMaterial)
}
Repeater {
id: categoryRepeater
- model: materialsModel
+ model: root.materialsModel
delegate: HelperWidgets.Section {
id: section
@@ -73,7 +73,7 @@ HelperWidgets.ScrollView {
bottomPadding: StudioTheme.Values.sectionPadding
caption: bundleCategoryName
- visible: bundleCategoryVisible && !materialsModel.isEmpty
+ visible: bundleCategoryVisible && !root.materialsModel.isEmpty
expanded: bundleCategoryExpanded
expandOnClick: false
category: "ContentLib_Mat"
@@ -103,7 +103,10 @@ HelperWidgets.ScrollView {
width: root.cellWidth
height: root.cellHeight
+ importerRunning: root.materialsModel.importerRunning
+
onShowContextMenu: ctxMenu.popupMenu(modelData)
+ onAddToProject: root.materialsModel.addToProject(modelData)
}
onCountChanged: root.assignMaxCount()
@@ -115,13 +118,13 @@ HelperWidgets.ScrollView {
Text {
id: infoText
text: {
- if (!materialsModel.matBundleExists)
+ if (!root.materialsModel.matBundleExists)
qsTr("No materials available. Make sure you have internet connection.")
else if (!ContentLibraryBackend.rootView.isQt6Project)
qsTr("<b>Content Library</b> materials are not supported in Qt5 projects.")
else if (!ContentLibraryBackend.rootView.hasQuick3DImport)
qsTr("To use <b>Content Library</b>, first add the QtQuick3D module in the <b>Components</b> view.")
- else if (!materialsModel.hasRequiredQuick3DImport)
+ else if (!root.materialsModel.hasRequiredQuick3DImport)
qsTr("To use <b>Content Library</b>, version 6.3 or later of the QtQuick3D module is required.")
else if (!ContentLibraryBackend.rootView.hasMaterialLibrary)
qsTr("<b>Content Library</b> is disabled inside a non-visual component.")
@@ -134,7 +137,7 @@ HelperWidgets.ScrollView {
font.pixelSize: StudioTheme.Values.baseFontSize
topPadding: 10
leftPadding: 10
- visible: materialsModel.isEmpty
+ visible: root.materialsModel.isEmpty
}
}
}
diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml
index 1fac9f2234..617b724e66 100644
--- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml
+++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexturesView.qml
@@ -27,9 +27,6 @@ HelperWidgets.ScrollView {
root.count = c
}
- property var currMaterialItem: null
- property var rootItem: null
-
required property var searchBox
required property var model
required property string sectionCategory
diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml
new file mode 100644
index 0000000000..d3d1dbad92
--- /dev/null
+++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml
@@ -0,0 +1,151 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import QtQuick
+import HelperWidgets as HelperWidgets
+import StudioControls as StudioControls
+import StudioTheme as StudioTheme
+import ContentLibraryBackend
+
+HelperWidgets.ScrollView {
+ id: root
+
+ clip: true
+ interactive: !ctxMenu.opened && !ContentLibraryBackend.rootView.isDragging
+ && !HelperWidgets.Controller.contextMenuOpened
+
+ property real cellWidth: 100
+ property real cellHeight: 120
+ property int numColumns: 4
+
+ property int count: 0
+ function assignMaxCount() {
+ let c = 0
+ for (let i = 0; i < categoryRepeater.count; ++i)
+ c = Math.max(c, categoryRepeater.itemAt(i)?.count ?? 0)
+
+ root.count = c
+ }
+
+ required property var searchBox
+
+ signal unimport(var bundleItem);
+
+ function closeContextMenu() {
+ ctxMenu.close()
+ }
+
+ function expandVisibleSections() {
+ for (let i = 0; i < categoryRepeater.count; ++i) {
+ let cat = categoryRepeater.itemAt(i)
+ if (cat.visible && !cat.expanded)
+ cat.expandSection()
+ }
+ }
+
+ Column {
+ ContentLibraryMaterialContextMenu {
+ id: ctxMenu
+
+ hasModelSelection: ContentLibraryBackend.userModel.hasModelSelection
+ importerRunning: ContentLibraryBackend.userModel.importerRunning
+
+ onApplyToSelected: (add) => ContentLibraryBackend.userModel.applyToSelected(ctxMenu.targetMaterial, add)
+
+ onUnimport: root.unimport(ctxMenu.targetMaterial)
+ onAddToProject: ContentLibraryBackend.userModel.addToProject(ctxMenu.targetMaterial)
+ }
+
+ Repeater {
+ id: categoryRepeater
+
+ model: ContentLibraryBackend.userModel
+
+ delegate: HelperWidgets.Section {
+ id: section
+
+ width: root.width
+ leftPadding: StudioTheme.Values.sectionPadding
+ rightPadding: StudioTheme.Values.sectionPadding
+ topPadding: StudioTheme.Values.sectionPadding
+ bottomPadding: StudioTheme.Values.sectionPadding
+
+ caption: categoryName
+ visible: categoryVisible
+ expanded: categoryExpanded
+ expandOnClick: false
+ category: "ContentLib_User"
+
+ onToggleExpand: categoryExpanded = !categoryExpanded
+ onExpand: categoryExpanded = true
+ onCollapse: categoryExpanded = false
+
+ function expandSection() {
+ categoryExpanded = true
+ }
+
+ property alias count: repeater.count
+
+ onCountChanged: root.assignMaxCount()
+
+ property int numVisibleItem: 1 // initially, the tab is invisible so this will be 0
+
+ Grid {
+ width: section.width - section.leftPadding - section.rightPadding
+ spacing: StudioTheme.Values.sectionGridSpacing
+ columns: root.numColumns
+
+ Repeater {
+ id: repeater
+ model: categoryItems
+
+ delegate: ContentLibraryMaterial {
+ width: root.cellWidth
+ height: root.cellHeight
+
+ importerRunning: ContentLibraryBackend.userModel.importerRunning
+
+ onShowContextMenu: ctxMenu.popupMenu(modelData)
+ onAddToProject: ContentLibraryBackend.userModel.addToProject(modelData)
+
+ onVisibleChanged: {
+ section.numVisibleItem += visible ? 1 : -1
+ }
+ }
+
+ onCountChanged: root.assignMaxCount()
+ }
+ }
+
+ Text {
+ text: qsTr("No match found.");
+ color: StudioTheme.Values.themeTextColor
+ font.pixelSize: StudioTheme.Values.baseFontSize
+ leftPadding: 10
+ visible: !searchBox.isEmpty() && section.numVisibleItem === 0
+ }
+ }
+ }
+
+ Text {
+ id: infoText
+ text: {
+ if (!ContentLibraryBackend.effectsModel.bundleExists)
+ qsTr("User bundle couldn't be found.")
+ else if (!ContentLibraryBackend.rootView.isQt6Project)
+ qsTr("<b>Content Library</b> is not supported in Qt5 projects.")
+ else if (!ContentLibraryBackend.rootView.hasQuick3DImport)
+ qsTr("To use <b>Content Library</b>, first add the QtQuick3D module in the <b>Components</b> view.")
+ else if (!ContentLibraryBackend.rootView.hasMaterialLibrary)
+ qsTr("<b>Content Library</b> is disabled inside a non-visual component.")
+ else
+ ""
+ }
+ color: StudioTheme.Values.themeTextColor
+ font.pixelSize: StudioTheme.Values.baseFontSize
+ topPadding: 10
+ leftPadding: 10
+ visible: ContentLibraryBackend.effectsModel.isEmpty
+ }
+ }
+}
diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/UnimportBundleMaterialDialog.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/UnimportBundleMaterialDialog.qml
index 48be045d8b..4385e3bf82 100644
--- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/UnimportBundleMaterialDialog.qml
+++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/UnimportBundleMaterialDialog.qml
@@ -12,24 +12,27 @@ import ContentLibraryBackend
StudioControls.Dialog {
id: root
- title: qsTr("Bundle material might be in use")
+ property var targetBundleItem
+ property var targetBundleLabel // "effect" or "material"
+ property var targetBundleModel
+
+ title: qsTr("Bundle %1 might be in use").arg(root.targetBundleLabel)
anchors.centerIn: parent
closePolicy: Popup.CloseOnEscape
implicitWidth: 300
modal: true
- property var targetBundleType // "effect" or "material"
- property var targetBundleItem
+ onOpened: warningText.forceActiveFocus()
contentItem: Column {
spacing: 20
width: parent.width
Text {
- id: folderNotEmpty
+ id: warningText
- text: qsTr("If the %1 you are removing is in use, it might cause the project to malfunction.\n\nAre you sure you want to remove the %1?")
- .arg(root.targetBundleType)
+ text: qsTr("If the %1 you are removing is in use, it might cause the project to malfunction.\n\nAre you sure you want to remove it?")
+ .arg(root.targetBundleLabel)
color: StudioTheme.Values.themeTextColor
wrapMode: Text.WordWrap
anchors.right: parent.right
@@ -49,11 +52,7 @@ StudioControls.Dialog {
text: qsTr("Remove")
onClicked: {
- if (root.targetBundleType === "material")
- ContentLibraryBackend.materialsModel.removeFromProject(root.targetBundleItem)
- else if (root.targetBundleType === "effect")
- ContentLibraryBackend.effectsModel.removeFromProject(root.targetBundleItem)
-
+ root.targetBundleModel.removeFromProject(root.targetBundleItem)
root.accept()
}
}
@@ -64,6 +63,4 @@ StudioControls.Dialog {
}
}
}
-
- onOpened: folderNotEmpty.forceActiveFocus()
}
diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt
index 01f03cc271..1c7bebffa3 100644
--- a/src/plugins/qmldesigner/CMakeLists.txt
+++ b/src/plugins/qmldesigner/CMakeLists.txt
@@ -826,6 +826,7 @@ extend_qtc_plugin(QmlDesigner
contentlibraryeffect.cpp contentlibraryeffect.h
contentlibraryeffectscategory.cpp contentlibraryeffectscategory.h
contentlibraryeffectsmodel.cpp contentlibraryeffectsmodel.h
+ contentlibraryusermodel.cpp contentlibraryusermodel.h
)
extend_qtc_plugin(QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.h
index f546ea98cd..90d8468fa1 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.h
@@ -3,9 +3,8 @@
#pragma once
-#include "qmldesignercorelib_global.h"
+#include "nodeinstanceglobal.h"
-#include <QDataStream>
#include <QObject>
#include <QUrl>
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp
new file mode 100644
index 0000000000..56ba8cc6c7
--- /dev/null
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp
@@ -0,0 +1,308 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "contentlibraryusermodel.h"
+
+#include "contentlibrarybundleimporter.h"
+#include "contentlibrarymaterial.h"
+#include "contentlibrarymaterialscategory.h"
+#include "contentlibrarywidget.h"
+
+#include <designerpaths.h>
+#include "qmldesignerconstants.h"
+
+#include <utils/algorithm.h>
+#include <utils/hostosinfo.h>
+#include <utils/qtcassert.h>
+
+#include <QCoreApplication>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QQmlEngine>
+#include <QStandardPaths>
+#include <QUrl>
+
+namespace QmlDesigner {
+
+ContentLibraryUserModel::ContentLibraryUserModel(ContentLibraryWidget *parent)
+ : QAbstractListModel(parent)
+ , m_widget(parent)
+{
+ m_userCategories = {tr("Materials")/*, tr("Textures"), tr("3D"), tr("Effects"), tr("2D components")*/}; // TODO
+
+ loadUserBundle();
+}
+
+int ContentLibraryUserModel::rowCount(const QModelIndex &) const
+{
+ return m_userCategories.size();
+}
+
+QVariant ContentLibraryUserModel::data(const QModelIndex &index, int role) const
+{
+ QTC_ASSERT(index.isValid() && index.row() < m_userCategories.size(), return {});
+ QTC_ASSERT(roleNames().contains(role), return {});
+
+ if (role == NameRole)
+ return m_userCategories.at(index.row());
+
+ if (role == ItemsRole) {
+ if (index.row() == 0)
+ return QVariant::fromValue(m_userMaterials);
+ if (index.row() == 1)
+ return QVariant::fromValue(m_userTextures);
+ if (index.row() == 2)
+ return QVariant::fromValue(m_user3DItems);
+ if (index.row() == 3)
+ return QVariant::fromValue(m_userEffects);
+ }
+
+ if (role == VisibleRole)
+ return true; // TODO
+
+ if (role == ExpandedRole)
+ return true; // TODO
+
+ return {};
+}
+
+bool ContentLibraryUserModel::isValidIndex(int idx) const
+{
+ return idx > -1 && idx < rowCount();
+}
+
+void ContentLibraryUserModel::updateIsEmpty()
+{
+ bool anyMatVisible = Utils::anyOf(m_userMaterials, [&](ContentLibraryMaterial *mat) {
+ return mat->visible();
+ });
+
+ bool newEmpty = !anyMatVisible || !m_widget->hasMaterialLibrary() || !hasRequiredQuick3DImport();
+
+ if (newEmpty != m_isEmpty) {
+ m_isEmpty = newEmpty;
+ emit isEmptyChanged();
+ }
+}
+
+QHash<int, QByteArray> ContentLibraryUserModel::roleNames() const
+{
+ static const QHash<int, QByteArray> roles {
+ {NameRole, "categoryName"},
+ {VisibleRole, "categoryVisible"},
+ {ExpandedRole, "categoryExpanded"},
+ {ItemsRole, "categoryItems"}
+ };
+ return roles;
+}
+
+void ContentLibraryUserModel::createImporter(const QString &bundlePath, const QString &bundleId,
+ const QStringList &sharedFiles)
+{
+ m_importer = new Internal::ContentLibraryBundleImporter(bundlePath, bundleId, sharedFiles);
+#ifdef QDS_USE_PROJECTSTORAGE
+ connect(m_importer,
+ &Internal::ContentLibraryBundleImporter::importFinished,
+ this,
+ [&](const QmlDesigner::TypeName &typeName) {
+ m_importerRunning = false;
+ emit importerRunningChanged();
+ if (typeName.size())
+ emit bundleMaterialImported(typeName);
+ });
+#else
+ connect(m_importer,
+ &Internal::ContentLibraryBundleImporter::importFinished,
+ this,
+ [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
+ m_importerRunning = false;
+ emit importerRunningChanged();
+ if (metaInfo.isValid())
+ emit bundleMaterialImported(metaInfo);
+ });
+#endif
+
+ connect(m_importer, &Internal::ContentLibraryBundleImporter::unimportFinished, this,
+ [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
+ Q_UNUSED(metaInfo)
+ m_importerRunning = false;
+ emit importerRunningChanged();
+ emit bundleMaterialUnimported(metaInfo);
+ });
+
+ resetModel();
+ updateIsEmpty();
+}
+
+void ContentLibraryUserModel::loadUserBundle()
+{
+ if (m_matBundleExists)
+ return;
+
+ QDir bundleDir{Paths::bundlesPathSetting() + "/User/materials"};
+
+ if (m_bundleObj.isEmpty()) {
+ QFile matsJsonFile(bundleDir.filePath("user_materials_bundle.json"));
+
+ if (!matsJsonFile.open(QIODevice::ReadOnly)) {
+ qWarning("Couldn't open user_materials_bundle.json");
+ return;
+ }
+
+ QJsonDocument matBundleJsonDoc = QJsonDocument::fromJson(matsJsonFile.readAll());
+ if (matBundleJsonDoc.isNull()) {
+ qWarning("Invalid user_materials_bundle.json file");
+ return;
+ } else {
+ m_bundleObj = matBundleJsonDoc.object();
+ }
+ }
+
+ QString bundleId = m_bundleObj.value("id").toString();
+
+ // parse materials
+ const QJsonObject matsObj = m_bundleObj.value("materials").toObject();
+ const QStringList materialNames = matsObj.keys();
+ for (const QString &matName : materialNames) {
+ const QJsonObject matObj = matsObj.value(matName).toObject();
+
+ QStringList files;
+ const QJsonArray assetsArr = matObj.value("files").toArray();
+ for (const auto /*QJson{Const,}ValueRef*/ &asset : assetsArr)
+ files.append(asset.toString());
+
+ QUrl icon = QUrl::fromLocalFile(bundleDir.filePath(matObj.value("icon").toString()));
+ QString qml = matObj.value("qml").toString();
+
+ TypeName type = QLatin1String("%1.%2.%3").arg(
+ QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1),
+ bundleId,
+ qml.chopped(4)).toLatin1(); // chopped(4): remove .qml
+
+ auto userMat = new ContentLibraryMaterial(this, matName, qml, type, icon, files,
+ bundleDir.path(), "");
+
+ m_userMaterials.append(userMat);
+ }
+
+ QStringList sharedFiles;
+ const QJsonArray sharedFilesArr = m_bundleObj.value("sharedFiles").toArray();
+ for (const auto /*QJson{Const,}ValueRef*/ &file : sharedFilesArr)
+ sharedFiles.append(file.toString());
+
+ createImporter(bundleDir.path(), bundleId, sharedFiles);
+
+ m_matBundleExists = true;
+ emit matBundleExistsChanged();
+}
+
+bool ContentLibraryUserModel::hasRequiredQuick3DImport() const
+{
+ return m_widget->hasQuick3DImport() && m_quick3dMajorVersion == 6 && m_quick3dMinorVersion >= 3;
+}
+
+bool ContentLibraryUserModel::matBundleExists() const
+{
+ return m_matBundleExists;
+}
+
+Internal::ContentLibraryBundleImporter *ContentLibraryUserModel::bundleImporter() const
+{
+ return m_importer;
+}
+
+void ContentLibraryUserModel::setSearchText(const QString &searchText)
+{
+ QString lowerSearchText = searchText.toLower();
+
+ if (m_searchText == lowerSearchText)
+ return;
+
+ m_searchText = lowerSearchText;
+
+ for (ContentLibraryMaterial *mat : std::as_const(m_userMaterials))
+ mat->filter(m_searchText);
+
+ updateIsEmpty();
+}
+
+void ContentLibraryUserModel::updateImportedState(const QStringList &importedMats)
+{
+ bool changed = false;
+
+ for (ContentLibraryMaterial *mat : std::as_const(m_userMaterials))
+ changed |= mat->setImported(importedMats.contains(mat->qml().chopped(4)));
+
+ if (changed)
+ resetModel();
+}
+
+void ContentLibraryUserModel::setQuick3DImportVersion(int major, int minor)
+{
+ bool oldRequiredImport = hasRequiredQuick3DImport();
+
+ m_quick3dMajorVersion = major;
+ m_quick3dMinorVersion = minor;
+
+ bool newRequiredImport = hasRequiredQuick3DImport();
+
+ if (oldRequiredImport == newRequiredImport)
+ return;
+
+ emit hasRequiredQuick3DImportChanged();
+
+ updateIsEmpty();
+}
+
+void ContentLibraryUserModel::resetModel()
+{
+ beginResetModel();
+ endResetModel();
+}
+
+void ContentLibraryUserModel::applyToSelected(ContentLibraryMaterial *mat, bool add)
+{
+ emit applyToSelectedTriggered(mat, add);
+}
+
+void ContentLibraryUserModel::addToProject(ContentLibraryMaterial *mat)
+{
+ QString err = m_importer->importComponent(mat->qml(), mat->files());
+
+ if (err.isEmpty()) {
+ m_importerRunning = true;
+ emit importerRunningChanged();
+ } else {
+ qWarning() << __FUNCTION__ << err;
+ }
+}
+
+void ContentLibraryUserModel::removeFromProject(ContentLibraryMaterial *mat)
+{
+ emit bundleMaterialAboutToUnimport(mat->type());
+
+ QString err = m_importer->unimportComponent(mat->qml());
+
+ if (err.isEmpty()) {
+ m_importerRunning = true;
+ emit importerRunningChanged();
+ } else {
+ qWarning() << __FUNCTION__ << err;
+ }
+}
+
+bool ContentLibraryUserModel::hasModelSelection() const
+{
+ return m_hasModelSelection;
+}
+
+void ContentLibraryUserModel::setHasModelSelection(bool b)
+{
+ if (b == m_hasModelSelection)
+ return;
+
+ m_hasModelSelection = b;
+ emit hasModelSelectionChanged();
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h
new file mode 100644
index 0000000000..72c535e401
--- /dev/null
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h
@@ -0,0 +1,118 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "nodemetainfo.h"
+
+#include <QAbstractListModel>
+#include <QJsonObject>
+
+namespace QmlDesigner {
+
+class ContentLibraryEffect;
+class ContentLibraryMaterial;
+class ContentLibraryTexture;
+class ContentLibraryWidget;
+
+namespace Internal {
+class ContentLibraryBundleImporter;
+}
+
+class ContentLibraryUserModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+ Q_PROPERTY(bool matBundleExists READ matBundleExists NOTIFY matBundleExistsChanged)
+ Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
+ Q_PROPERTY(bool hasRequiredQuick3DImport READ hasRequiredQuick3DImport NOTIFY hasRequiredQuick3DImportChanged)
+ Q_PROPERTY(bool hasModelSelection READ hasModelSelection NOTIFY hasModelSelectionChanged)
+ Q_PROPERTY(bool importerRunning MEMBER m_importerRunning NOTIFY importerRunningChanged)
+ Q_PROPERTY(QList<ContentLibraryMaterial *> userMaterials MEMBER m_userMaterials NOTIFY userMaterialsChanged)
+ Q_PROPERTY(QList<ContentLibraryTexture *> userTextures MEMBER m_userTextures NOTIFY userTexturesChanged)
+ Q_PROPERTY(QList<ContentLibraryEffect *> user3DItems MEMBER m_user3DItems NOTIFY user3DItemsChanged)
+ Q_PROPERTY(QList<ContentLibraryEffect *> userEffects MEMBER m_userEffects NOTIFY userEffectsChanged)
+
+public:
+ ContentLibraryUserModel(ContentLibraryWidget *parent = nullptr);
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ void setSearchText(const QString &searchText);
+ void updateImportedState(const QStringList &importedMats);
+
+ void setQuick3DImportVersion(int major, int minor);
+
+ bool hasRequiredQuick3DImport() const;
+
+ bool matBundleExists() const;
+
+ bool hasModelSelection() const;
+ void setHasModelSelection(bool b);
+
+ void resetModel();
+ void updateIsEmpty();
+
+ Internal::ContentLibraryBundleImporter *bundleImporter() const;
+
+ Q_INVOKABLE void applyToSelected(QmlDesigner::ContentLibraryMaterial *mat, bool add = false);
+ Q_INVOKABLE void addToProject(QmlDesigner::ContentLibraryMaterial *mat);
+ Q_INVOKABLE void removeFromProject(QmlDesigner::ContentLibraryMaterial *mat);
+
+signals:
+ void isEmptyChanged();
+ void hasRequiredQuick3DImportChanged();
+ void hasModelSelectionChanged();
+ void userMaterialsChanged();
+ void userTexturesChanged();
+ void user3DItemsChanged();
+ void userEffectsChanged();
+
+ void applyToSelectedTriggered(QmlDesigner::ContentLibraryMaterial *mat, bool add = false);
+
+#ifdef QDS_USE_PROJECTSTORAGE
+ void bundleMaterialImported(const QmlDesigner::TypeName &typeName);
+#else
+ void bundleMaterialImported(const QmlDesigner::NodeMetaInfo &metaInfo);
+#endif
+ void bundleMaterialAboutToUnimport(const QmlDesigner::TypeName &type);
+ void bundleMaterialUnimported(const QmlDesigner::NodeMetaInfo &metaInfo);
+ void importerRunningChanged();
+ void matBundleExistsChanged();
+
+private:
+ void loadUserBundle();
+ bool isValidIndex(int idx) const;
+ void createImporter(const QString &bundlePath, const QString &bundleId,
+ const QStringList &sharedFiles);
+
+ ContentLibraryWidget *m_widget = nullptr;
+ QString m_searchText;
+
+ QList<ContentLibraryMaterial *> m_userMaterials;
+ QList<ContentLibraryTexture *> m_userTextures;
+ QList<ContentLibraryEffect *> m_userEffects;
+ QList<ContentLibraryEffect *> m_user3DItems;
+ QStringList m_userCategories;
+
+ QJsonObject m_bundleObj;
+ Internal::ContentLibraryBundleImporter *m_importer = nullptr;
+
+ bool m_isEmpty = true;
+ bool m_matBundleExists = false;
+ bool m_hasModelSelection = false;
+ bool m_importerRunning = false;
+
+ int m_quick3dMajorVersion = -1;
+ int m_quick3dMinorVersion = -1;
+
+ QString m_importerBundlePath;
+ QString m_importerBundleId;
+ QStringList m_importerSharedFiles;
+
+ enum Roles { NameRole = Qt::UserRole + 1, VisibleRole, ExpandedRole, ItemsRole };
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp
index 61ae078ea8..37ad648c3a 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp
@@ -10,6 +10,7 @@
#include "contentlibrarymaterialsmodel.h"
#include "contentlibrarytexture.h"
#include "contentlibrarytexturesmodel.h"
+#include "contentlibraryusermodel.h"
#include "contentlibrarywidget.h"
#include "externaldependenciesinterface.h"
#include "nodelistproperty.h"
@@ -204,6 +205,8 @@ WidgetInfo ContentLibraryView::widgetInfo()
connect(effectsModel, &ContentLibraryEffectsModel::bundleItemUnimported, this,
&ContentLibraryView::updateBundleEffectsImportedState);
+
+ connectUserBundle();
}
return createWidgetInfo(m_widget.data(),
@@ -213,6 +216,64 @@ WidgetInfo ContentLibraryView::widgetInfo()
tr("Content Library"));
}
+void ContentLibraryView::connectUserBundle()
+{
+ ContentLibraryUserModel *userModel = m_widget->userModel().data();
+
+ connect(userModel,
+ &ContentLibraryUserModel::applyToSelectedTriggered,
+ this,
+ [&](ContentLibraryMaterial *bundleMat, bool add) {
+ if (m_selectedModels.isEmpty())
+ return;
+
+ m_bundleMaterialTargets = m_selectedModels;
+ m_bundleMaterialAddToSelected = add;
+
+ ModelNode defaultMat = getBundleMaterialDefaultInstance(bundleMat->type());
+ if (defaultMat.isValid())
+ applyBundleMaterialToDropTarget(defaultMat);
+ else
+ m_widget->userModel()->addToProject(bundleMat);
+ });
+
+#ifdef QDS_USE_PROJECTSTORAGE
+ connect(userModel,
+ &ContentLibraryUserModel::bundleMaterialImported,
+ this,
+ [&](const QmlDesigner::TypeName &typeName) {
+ applyBundleMaterialToDropTarget({}, typeName);
+ updateBundleUserMaterialsImportedState();
+ });
+#else
+ connect(userModel,
+ &ContentLibraryUserModel::bundleMaterialImported,
+ this,
+ [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
+ applyBundleMaterialToDropTarget({}, metaInfo);
+ updateBundleUserMaterialsImportedState();
+ });
+#endif
+
+ connect(userModel, &ContentLibraryUserModel::bundleMaterialAboutToUnimport, this,
+ [&] (const QmlDesigner::TypeName &type) {
+ // delete instances of the bundle material that is about to be unimported
+ executeInTransaction("ContentLibraryView::connectUserModel", [&] {
+ ModelNode matLib = Utils3D::materialLibraryNode(this);
+ if (!matLib.isValid())
+ return;
+
+ Utils::reverseForeach(matLib.directSubModelNodes(), [&](const ModelNode &mat) {
+ if (mat.isValid() && mat.type() == type)
+ QmlObjectNode(mat).destroy();
+ });
+ });
+ });
+
+ connect(userModel, &ContentLibraryUserModel::bundleMaterialUnimported, this,
+ &ContentLibraryView::updateBundleUserMaterialsImportedState);
+}
+
void ContentLibraryView::modelAttached(Model *model)
{
AbstractView::modelAttached(model);
@@ -276,6 +337,7 @@ void ContentLibraryView::selectedNodesChanged(const QList<ModelNode> &selectedNo
});
m_widget->materialsModel()->setHasModelSelection(!m_selectedModels.isEmpty());
+ m_widget->userModel()->setHasModelSelection(!m_selectedModels.isEmpty());
}
void ContentLibraryView::customNotification(const AbstractView *view,
@@ -548,6 +610,25 @@ void ContentLibraryView::updateBundleMaterialsImportedState()
m_widget->materialsModel()->updateImportedState(importedBundleMats);
}
+void ContentLibraryView::updateBundleUserMaterialsImportedState()
+{
+ using namespace Utils;
+
+ if (!m_widget->userModel()->bundleImporter())
+ return;
+
+ QStringList importedBundleMats;
+
+ FilePath bundlePath = m_widget->userModel()->bundleImporter()->resolveBundleImportPath();
+
+ if (bundlePath.exists()) {
+ importedBundleMats = transform(bundlePath.dirEntries({{"*.qml"}, QDir::Files}),
+ [](const FilePath &f) { return f.fileName().chopped(4); });
+ }
+
+ m_widget->userModel()->updateImportedState(importedBundleMats);
+}
+
void ContentLibraryView::updateBundleEffectsImportedState()
{
using namespace Utils;
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h
index 3b57b7a4ab..48a8fd98f4 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h
@@ -46,8 +46,10 @@ public:
const QVariant &data) override;
private:
+ void connectUserBundle();
void active3DSceneChanged(qint32 sceneId);
void updateBundleMaterialsImportedState();
+ void updateBundleUserMaterialsImportedState();
void updateBundleEffectsImportedState();
void updateBundlesQuick3DVersion();
#ifdef QDS_USE_PROJECTSTORAGE
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp
index c885a76ba7..8a2e81cfb2 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp
@@ -10,6 +10,7 @@
#include "contentlibrarytexture.h"
#include "contentlibrarytexturesmodel.h"
#include "contentlibraryiconprovider.h"
+#include "contentlibraryusermodel.h"
#include "utils/filedownloader.h"
#include "utils/fileextractor.h"
@@ -126,6 +127,7 @@ ContentLibraryWidget::ContentLibraryWidget()
, m_texturesModel(new ContentLibraryTexturesModel("Textures", this))
, m_environmentsModel(new ContentLibraryTexturesModel("Environments", this))
, m_effectsModel(new ContentLibraryEffectsModel(this))
+ , m_userModel(new ContentLibraryUserModel(this))
{
qmlRegisterType<QmlDesigner::FileDownloader>("WebFetcher", 1, 0, "FileDownloader");
qmlRegisterType<QmlDesigner::FileExtractor>("WebFetcher", 1, 0, "FileExtractor");
@@ -177,7 +179,8 @@ ContentLibraryWidget::ContentLibraryWidget()
{"materialsModel", QVariant::fromValue(m_materialsModel.data())},
{"texturesModel", QVariant::fromValue(m_texturesModel.data())},
{"environmentsModel", QVariant::fromValue(m_environmentsModel.data())},
- {"effectsModel", QVariant::fromValue(m_effectsModel.data())}});
+ {"effectsModel", QVariant::fromValue(m_effectsModel.data())},
+ {"userModel", QVariant::fromValue(m_userModel.data())}});
reloadQmlSource();
}
@@ -601,6 +604,12 @@ void ContentLibraryWidget::markTextureUpdated(const QString &textureKey)
m_environmentsModel->markTextureHasNoUpdates(subcategory, textureKey);
}
+bool ContentLibraryWidget::userBundleEnabled() const
+{
+ // TODO: this method is to be removed after user bundle implementation is complete
+ return Core::ICore::settings()->value("QML/Designer/UseExperimentalFeatures45", false).toBool();
+}
+
QSize ContentLibraryWidget::sizeHint() const
{
return {420, 420};
@@ -715,6 +724,7 @@ void ContentLibraryWidget::updateSearch()
m_effectsModel->setSearchText(m_filterText);
m_texturesModel->setSearchText(m_filterText);
m_environmentsModel->setSearchText(m_filterText);
+ m_userModel->setSearchText(m_filterText);
m_quickWidget->update();
}
@@ -821,4 +831,9 @@ QPointer<ContentLibraryEffectsModel> ContentLibraryWidget::effectsModel() const
return m_effectsModel;
}
+QPointer<ContentLibraryUserModel> ContentLibraryWidget::userModel() const
+{
+ return m_userModel;
+}
+
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h
index ab71a3dc79..729443817e 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h
@@ -24,6 +24,7 @@ class ContentLibraryMaterial;
class ContentLibraryMaterialsModel;
class ContentLibraryTexture;
class ContentLibraryTexturesModel;
+class ContentLibraryUserModel;
class ContentLibraryWidget : public QFrame
{
@@ -65,6 +66,7 @@ public:
QPointer<ContentLibraryTexturesModel> texturesModel() const;
QPointer<ContentLibraryTexturesModel> environmentsModel() const;
QPointer<ContentLibraryEffectsModel> effectsModel() const;
+ QPointer<ContentLibraryUserModel> userModel() const;
Q_INVOKABLE void startDragEffect(QmlDesigner::ContentLibraryEffect *eff, const QPointF &mousePos);
Q_INVOKABLE void startDragMaterial(QmlDesigner::ContentLibraryMaterial *mat, const QPointF &mousePos);
@@ -74,6 +76,7 @@ public:
Q_INVOKABLE void addLightProbe(QmlDesigner::ContentLibraryTexture *tex);
Q_INVOKABLE void updateSceneEnvState();
Q_INVOKABLE void markTextureUpdated(const QString &textureKey);
+ Q_INVOKABLE bool userBundleEnabled() const;
QSize sizeHint() const override;
@@ -112,6 +115,7 @@ private:
QPointer<ContentLibraryTexturesModel> m_texturesModel;
QPointer<ContentLibraryTexturesModel> m_environmentsModel;
QPointer<ContentLibraryEffectsModel> m_effectsModel;
+ QPointer<ContentLibraryUserModel> m_userModel;
QShortcut *m_qmlSourceUpdateShortcut = nullptr;