diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/ItemBorder.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/ItemBorder.qml new file mode 100644 index 00000000000..a7ea85078e7 --- /dev/null +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/ItemBorder.qml @@ -0,0 +1,38 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Shapes +import StudioTheme as StudioTheme + +Shape { + id: root + + property bool selected: false + property bool rightClicked: false + + anchors.fill: parent + + ShapePath { + strokeWidth: root.rightClicked ? 2 : 0 + fillColor: "transparent" + strokeColor: StudioTheme.Values.themeInteractionHover + strokeStyle: ShapePath.DashLine + dashPattern: [ 2, 4 ] + PathRectangle { + width: root.width + height: root.height + } + } + + ShapePath { + strokeWidth: root.selected ? 1 : 0 + strokeColor: StudioTheme.Values.themeControlOutlineInteraction + fillColor: "transparent" + strokeStyle: ShapePath.PathLinear + PathRectangle { + width: root.width + height: root.height + } + } +} diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index 06f5d3c2063..e173d10af1c 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -16,7 +16,9 @@ Item { readonly property bool enableUiElements: materialBrowserModel.hasMaterialLibrary && materialBrowserModel.hasQuick3DImport - property var currMaterialItem: null + property MaterialItem currMaterialItem: null + property TextureItem currTextureItem: null + property var rootView: MaterialBrowserBackend.rootView property var materialBrowserModel: MaterialBrowserBackend.materialBrowserModel property var materialBrowserTexturesModel: MaterialBrowserBackend.materialBrowserTexturesModel @@ -64,7 +66,7 @@ Item { // Called from C++ to refresh a preview material after it changes function refreshPreview(idx) { - var item = materialRepeater.itemAt(idx); + var item = materialRepeater.itemAt(idx) if (item) item.refreshPreview() } @@ -135,15 +137,18 @@ Item { let rowIdx = -1 let matSecFocused = rootView.materialSectionFocused && materialsSection.expanded let texSecFocused = !rootView.materialSectionFocused && texturesSection.expanded + let selectedMaterialIndex = root.currMaterialItem ? root.currMaterialItem.itemIndex : -1 + let selectedTextureIndex = root.currTextureItem ? root.currTextureItem.itemIndex : -1 if (delta < 0) { if (matSecFocused) { - targetIdx = root.nextVisibleItem(materialBrowserModel.selectedIndex, + targetIdx = root.nextVisibleItem(selectedMaterialIndex, delta, materialBrowserModel) + if (targetIdx >= 0) materialBrowserModel.selectMaterial(targetIdx) } else if (texSecFocused) { - targetIdx = root.nextVisibleItem(materialBrowserTexturesModel.selectedIndex, + targetIdx = root.nextVisibleItem(selectedTextureIndex, delta, materialBrowserTexturesModel) if (targetIdx >= 0) { materialBrowserTexturesModel.selectTexture(targetIdx) @@ -152,7 +157,7 @@ Item { if (targetIdx >= 0) { if (delta !== -1) { // Try to match column when switching between materials/textures - origRowIdx = root.rowIndexOfItem(materialBrowserTexturesModel.selectedIndex, + origRowIdx = root.rowIndexOfItem(selectedTextureIndex, -delta, materialBrowserTexturesModel) if (root.visibleItemCount(materialBrowserModel) > origRowIdx) { rowIdx = root.rowIndexOfItem(targetIdx, -delta, materialBrowserModel) @@ -179,7 +184,7 @@ Item { } } else if (delta > 0) { if (matSecFocused) { - targetIdx = root.nextVisibleItem(materialBrowserModel.selectedIndex, + targetIdx = root.nextVisibleItem(selectedMaterialIndex, delta, materialBrowserModel) if (targetIdx >= 0) { materialBrowserModel.selectMaterial(targetIdx) @@ -188,7 +193,7 @@ Item { if (targetIdx >= 0) { if (delta !== 1) { // Try to match column when switching between materials/textures - origRowIdx = root.rowIndexOfItem(materialBrowserModel.selectedIndex, + origRowIdx = root.rowIndexOfItem(selectedMaterialIndex, delta, materialBrowserModel) if (root.visibleItemCount(materialBrowserTexturesModel) > origRowIdx) { if (origRowIdx > 0) { @@ -207,7 +212,7 @@ Item { } } } else if (texSecFocused) { - targetIdx = root.nextVisibleItem(materialBrowserTexturesModel.selectedIndex, + targetIdx = root.nextVisibleItem(selectedTextureIndex, delta, materialBrowserTexturesModel) if (targetIdx >= 0) materialBrowserTexturesModel.selectTexture(targetIdx) @@ -300,14 +305,13 @@ Item { function ensureSelectedVisible() { if (rootView.materialSectionFocused && materialsSection.expanded && root.currMaterialItem - && materialBrowserModel.isVisible(materialBrowserModel.selectedIndex)) { + && root.currMaterialItem.matchedSearch) { return root.ensureVisible(root.currMaterialItem.mapToItem(scrollView.contentItem, 0, 0).y, root.currMaterialItem.height) - } else if (!rootView.materialSectionFocused && texturesSection.expanded) { - let currItem = texturesRepeater.itemAt(materialBrowserTexturesModel.selectedIndex) - if (currItem && materialBrowserTexturesModel.isVisible(materialBrowserTexturesModel.selectedIndex)) - return root.ensureVisible(currItem.mapToItem(scrollView.contentItem, 0, 0).y, - currItem.height) + } else if (!rootView.materialSectionFocused && texturesSection.expanded && root.currTextureItem + && root.currTextureItem.matchedSearch) { + return root.ensureVisible(root.currTextureItem.mapToItem(scrollView.contentItem, 0, 0).y, + root.currTextureItem.height) } else { return root.ensureVisible(0, 90) } @@ -341,16 +345,6 @@ Item { Connections { target: materialBrowserModel - function onSelectedIndexChanged() { - // commit rename upon changing selection - if (root.currMaterialItem) - root.currMaterialItem.forceFinishEditing(); - - root.currMaterialItem = materialRepeater.itemAt(materialBrowserModel.selectedIndex); - - ensureTimer.start() - } - function onIsEmptyChanged() { ensureTimer.start() } @@ -359,10 +353,6 @@ Item { Connections { target: materialBrowserTexturesModel - function onSelectedIndexChanged() { - ensureTimer.start() - } - function onIsEmptyChanged() { ensureTimer.start() } @@ -706,10 +696,24 @@ Item { } delegate: MaterialItem { + id: matItem + width: root.cellWidth height: root.cellHeight + rightClicked: ctxMenu.targetItem === this onShowContextMenu: ctxMenu.popupMenu(this, model) + + onSelectedChanged: { + matItem.forceFinishEditing() + + if (matItem.selected) { + root.currMaterialItem = this + ensureTimer.start() + } else if (root.currMaterialItem === this) { + root.currMaterialItem = null + } + } } onCountChanged: root.responsiveResize(root.width, root.height) @@ -717,7 +721,7 @@ Item { } Text { - text: qsTr("No match found."); + text: qsTr("No match found.") color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize leftPadding: 10 @@ -801,10 +805,24 @@ Item { model: materialBrowserTexturesModel delegate: TextureItem { + id: texItem + width: root.cellWidth height: root.cellHeight + rightClicked: ctxMenuTextures.textureIndex === index onShowContextMenu: ctxMenuTextures.popupMenu(model) + + onSelectedChanged: { + texItem.forceFinishEditing() + + if (texItem.selected) { + root.currTextureItem = this + ensureTimer.start() + } else if (root.currTextureItem === this) { + root.currTextureItem = null + } + } } onCountChanged: root.responsiveResize(root.width, root.height) @@ -812,7 +830,7 @@ Item { } Text { - text: qsTr("No match found."); + text: qsTr("No match found.") color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.baseFontSize leftPadding: 10 diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowserContextMenu.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowserContextMenu.qml index cf28242b22e..a47b983bd46 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowserContextMenu.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowserContextMenu.qml @@ -12,6 +12,7 @@ StudioControls.Menu { property var targetMaterial: null property var targetItem: null + property int targetIndex: -1 property int copiedMaterialInternalId: -1 property var matSectionsModel: [] property bool restoreFocusOnClose: true @@ -22,12 +23,19 @@ StudioControls.Menu { { this.targetItem = targetItem this.targetMaterial = targetMaterial + this.targetIndex = targetMaterial ? targetMaterial.index : -1 restoreFocusOnClose = true popup() } closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside + onClosed: { + this.targetItem = null + this.targetMaterial = null + this.targetIndex = -1 + } + StudioControls.MenuItem { text: qsTr("Apply to selected (replace)") enabled: root.targetMaterial && materialBrowserModel.hasModelSelection @@ -50,26 +58,26 @@ StudioControls.Menu { onAboutToShow: { if (root.targetMaterial.hasDynamicProperties) - root.matSectionsModel = ["All", "Custom"]; + root.matSectionsModel = ["All", "Custom"] else - root.matSectionsModel = ["All"]; + root.matSectionsModel = ["All"] switch (root.targetMaterial.materialType) { case "DefaultMaterial": - root.matSectionsModel = root.matSectionsModel.concat(materialBrowserModel.defaultMaterialSections); - break; + root.matSectionsModel = root.matSectionsModel.concat(materialBrowserModel.defaultMaterialSections) + break case "PrincipledMaterial": - root.matSectionsModel = root.matSectionsModel.concat(materialBrowserModel.principledMaterialSections); - break; + root.matSectionsModel = root.matSectionsModel.concat(materialBrowserModel.principledMaterialSections) + break case "SpecularGlossyMaterial": - root.matSectionsModel = root.matSectionsModel.concat(materialBrowserModel.specularGlossyMaterialSections); - break; + root.matSectionsModel = root.matSectionsModel.concat(materialBrowserModel.specularGlossyMaterialSections) + break case "CustomMaterial": - root.matSectionsModel = root.matSectionsModel.concat(materialBrowserModel.customMaterialSections); - break; + root.matSectionsModel = root.matSectionsModel.concat(materialBrowserModel.customMaterialSections) + break } } @@ -81,7 +89,7 @@ StudioControls.Menu { enabled: root.targetMaterial onTriggered: { root.copiedMaterialInternalId = root.targetMaterial.materialInternalId - materialBrowserModel.copyMaterialProperties(materialBrowserModel.selectedIndex, modelData) + materialBrowserModel.copyMaterialProperties(root.targetIndex, modelData) } } } @@ -93,7 +101,7 @@ StudioControls.Menu { && root.copiedMaterialInternalId !== root.targetMaterial.materialInternalId && root.targetMaterial.materialType === materialBrowserModel.copiedMaterialType && materialBrowserModel.isCopiedMaterialValid() - onTriggered: materialBrowserModel.pasteMaterialProperties(materialBrowserModel.selectedIndex) + onTriggered: materialBrowserModel.pasteMaterialProperties(root.targetIndex) } StudioControls.MenuSeparator {} @@ -101,7 +109,7 @@ StudioControls.Menu { StudioControls.MenuItem { text: qsTr("Duplicate") enabled: root.targetMaterial - onTriggered: materialBrowserModel.duplicateMaterial(materialBrowserModel.selectedIndex) + onTriggered: materialBrowserModel.duplicateMaterial(root.targetIndex) } StudioControls.MenuItem { @@ -117,7 +125,7 @@ StudioControls.Menu { text: qsTr("Delete") enabled: root.targetMaterial - onTriggered: materialBrowserModel.deleteMaterial(materialBrowserModel.selectedIndex) + onTriggered: materialBrowserModel.deleteMaterial(root.targetIndex) } StudioControls.MenuSeparator {} @@ -131,7 +139,7 @@ StudioControls.Menu { StudioControls.MenuItem { text: qsTr("Add to Content Library") - onTriggered: MaterialBrowserBackend.rootView.addMaterialToContentLibrary() + onTriggered: MaterialBrowserBackend.rootView.addMaterialToContentLibrary(root.targetMaterial) } StudioControls.MenuItem { @@ -142,8 +150,8 @@ StudioControls.Menu { StudioControls.MenuItem { text: qsTr("Export Material") - enabled: !materialBrowserModel.selectedMaterialIsComponent // TODO: support component materials + enabled: root.targetMaterial && !root.targetMaterial.materialIsComponent // TODO: support component materials - onTriggered: MaterialBrowserBackend.rootView.exportMaterial() + onTriggered: MaterialBrowserBackend.rootView.exportMaterial(root.targetIndex) } } diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowserItemName.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowserItemName.qml index eb599fa0c3b..4bf1f3b8437 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowserItemName.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowserItemName.qml @@ -26,7 +26,7 @@ TextInput { validator: RegularExpressionValidator { regularExpression: /^(\w+\s)*\w+$/ } signal renamed(string newName) - signal clicked() + signal clicked(var mouseEvent) function startRename() { @@ -40,7 +40,7 @@ TextInput { function commitRename() { if (root.readOnly) - return; + return root.renamed(root.text) } @@ -61,7 +61,7 @@ TextInput { id: mouseArea anchors.fill: parent - onClicked: root.clicked() + onClicked: (mouseEvent) => root.clicked(mouseEvent) onDoubleClicked: root.startRename() } } diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml index 47eed8a7d6b..1846dc41dc9 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialItem.qml @@ -10,6 +10,11 @@ import MaterialBrowserBackend Item { id: root + readonly property bool selected: materialSelected?? false + readonly property bool matchedSearch: materialMatchedSearch?? false + readonly property int itemIndex: index + property bool rightClicked: false + signal showContextMenu() function refreshPreview() { @@ -25,7 +30,7 @@ Item { matName.startRename() } - visible: materialVisible + visible: matchedSearch DropArea { anchors.fill: parent @@ -55,17 +60,20 @@ Item { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton - onPressed: (mouse) => { - MaterialBrowserBackend.materialBrowserModel.selectMaterial(index) + function handleClick(mouse) { MaterialBrowserBackend.rootView.focusMaterialSection(true) - if (mouse.button === Qt.LeftButton) + if (mouse.button === Qt.LeftButton) { + let appendMat = mouse.modifiers & Qt.ControlModifier MaterialBrowserBackend.rootView.startDragMaterial(index, mapToGlobal(mouse.x, mouse.y)) - else if (mouse.button === Qt.RightButton) + MaterialBrowserBackend.materialBrowserModel.selectMaterial(index, appendMat) + } else if (mouse.button === Qt.RightButton) { root.showContextMenu() + } } - onDoubleClicked: MaterialBrowserBackend.materialBrowserModel.openMaterialEditor(); + onPressed: (mouse) => handleClick(mouse) + onDoubleClicked: MaterialBrowserBackend.materialBrowserModel.openMaterialEditor() } Column { @@ -95,24 +103,16 @@ Item { anchors.horizontalCenter: parent.horizontalCenter onRenamed: (newName) => { - MaterialBrowserBackend.materialBrowserModel.renameMaterial(index, newName); + MaterialBrowserBackend.materialBrowserModel.renameMaterial(index, newName) mouseArea.forceActiveFocus() } - onClicked: { - MaterialBrowserBackend.materialBrowserModel.selectMaterial(index) - MaterialBrowserBackend.rootView.focusMaterialSection(true) - } + onClicked: (mouse) => mouseArea.handleClick(mouse) } } - Rectangle { - id: marker - anchors.fill: parent - border.width: MaterialBrowserBackend.materialBrowserModel.selectedIndex === index ? MaterialBrowserBackend.rootView.materialSectionFocused ? 3 : 1 : 0 - border.color: MaterialBrowserBackend.materialBrowserModel.selectedIndex === index - ? StudioTheme.Values.themeControlOutlineInteraction - : "transparent" - color: "transparent" + ItemBorder { + selected: root.selected + rightClicked: root.rightClicked } } diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml index 99f398c8fbb..ebf367f675f 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureBrowserContextMenu.qml @@ -11,21 +11,28 @@ StudioControls.Menu { id: root property int textureInternalId: -1 + property int textureIndex: -1 property var materialBrowserTexturesModel: MaterialBrowserBackend.materialBrowserTexturesModel function popupMenu(targetTexture = null) { root.textureInternalId = targetTexture ? targetTexture.textureInternalId : -1 + root.textureIndex = targetTexture ? targetTexture.index : -1 materialBrowserTexturesModel.updateSceneEnvState() - materialBrowserTexturesModel.updateModelSelectionState() + materialBrowserTexturesModel.updateSelectionState() popup() } closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside + onClosed: { + root.textureIndex = -1 + root.textureInternalId = -1 + } + StudioControls.MenuItem { text: qsTr("Apply to selected model") enabled: root.textureInternalId >= 0 && materialBrowserTexturesModel.hasSingleModelSelection @@ -33,8 +40,8 @@ StudioControls.Menu { } StudioControls.MenuItem { - text: qsTr("Apply to selected material") - enabled: root.textureInternalId >= 0 && MaterialBrowserBackend.materialBrowserModel.selectedIndex >= 0 + text: qsTr("Apply to selected material(s)") + enabled: root.textureInternalId >= 0 && materialBrowserTexturesModel.onlyMaterialsSelected onTriggered: materialBrowserTexturesModel.applyToSelectedMaterial(root.textureInternalId) } @@ -49,13 +56,13 @@ StudioControls.Menu { StudioControls.MenuItem { text: qsTr("Duplicate") enabled: root.textureInternalId >= 0 - onTriggered: materialBrowserTexturesModel.duplicateTexture(materialBrowserTexturesModel.selectedIndex) + onTriggered: materialBrowserTexturesModel.duplicateTexture(root.textureIndex) } StudioControls.MenuItem { text: qsTr("Delete") enabled: root.textureInternalId >= 0 - onTriggered: materialBrowserTexturesModel.deleteTexture(materialBrowserTexturesModel.selectedIndex) + onTriggered: materialBrowserTexturesModel.deleteTexture(root.textureIndex) } StudioControls.MenuSeparator {} diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml index 19f656e51e5..7b53c8445f4 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/TextureItem.qml @@ -11,7 +11,12 @@ import MaterialBrowserBackend Item { id: root - visible: textureVisible + readonly property bool selected: textureSelected?? false + readonly property bool matchedSearch: textureMatchedSearch?? false + readonly property int itemIndex: index + property bool rightClicked: false + + visible: matchedSearch signal showContextMenu() @@ -26,17 +31,20 @@ Item { acceptedButtons: Qt.LeftButton | Qt.RightButton hoverEnabled: true - onPressed: (mouse) => { - MaterialBrowserBackend.materialBrowserTexturesModel.selectTexture(index) + function handleClick(mouse) { MaterialBrowserBackend.rootView.focusMaterialSection(false) - if (mouse.button === Qt.LeftButton) + if (mouse.button === Qt.LeftButton) { + let appendTexture = mouse.modifiers & Qt.ControlModifier + MaterialBrowserBackend.materialBrowserTexturesModel.selectTexture(index, appendTexture) MaterialBrowserBackend.rootView.startDragTexture(index, mapToGlobal(mouse.x, mouse.y)) - else if (mouse.button === Qt.RightButton) + } else if (mouse.button === Qt.RightButton) { root.showContextMenu() + } } - onDoubleClicked: MaterialBrowserBackend.materialBrowserTexturesModel.openTextureEditor(); + onPressed: (mouse) => handleClick(mouse) + onDoubleClicked: MaterialBrowserBackend.materialBrowserTexturesModel.openTextureEditor() } ToolTip { @@ -83,26 +91,16 @@ Item { anchors.horizontalCenter: parent.horizontalCenter onRenamed: (newName) => { - MaterialBrowserBackend.materialBrowserTexturesModel.setTextureName(index, newName); + MaterialBrowserBackend.materialBrowserTexturesModel.setTextureName(index, newName) mouseArea.forceActiveFocus() } - onClicked: { - MaterialBrowserBackend.materialBrowserTexturesModel.selectTexture(index) - MaterialBrowserBackend.rootView.focusMaterialSection(false) - } + onClicked: (mouse) => mouseArea.handleClick(mouse) } } - Rectangle { - id: marker - anchors.fill: parent - - color: "transparent" - border.width: MaterialBrowserBackend.materialBrowserTexturesModel.selectedIndex === index - ? !MaterialBrowserBackend.rootView.materialSectionFocused ? 3 : 1 : 0 - border.color: MaterialBrowserBackend.materialBrowserTexturesModel.selectedIndex === index - ? StudioTheme.Values.themeControlOutlineInteraction - : "transparent" + ItemBorder { + selected: root.selected + rightClicked: root.rightClicked } } diff --git a/src/plugins/qmldesigner/components/componentcore/utils3d.cpp b/src/plugins/qmldesigner/components/componentcore/utils3d.cpp index 1673c854d6b..13f24d42fd1 100644 --- a/src/plugins/qmldesigner/components/componentcore/utils3d.cpp +++ b/src/plugins/qmldesigner/components/componentcore/utils3d.cpp @@ -234,6 +234,26 @@ QList getSelectedModels(AbstractView *view) }); } +QList getSelectedTextures(AbstractView *view) +{ + if (!view || !view->model()) + return {}; + + return Utils::filtered(view->selectedModelNodes(), [](const ModelNode &node) { + return node.metaInfo().isQtQuick3DTexture(); + }); +} + +QList getSelectedMaterials(AbstractView *view) +{ + if (!view || !view->model()) + return {}; + + return Utils::filtered(view->selectedModelNodes(), [](const ModelNode &node) { + return node.metaInfo().isQtQuick3DMaterial(); + }); +} + void applyMaterialToModels(AbstractView *view, const ModelNode &material, const QList &models, bool add) { diff --git a/src/plugins/qmldesigner/components/componentcore/utils3d.h b/src/plugins/qmldesigner/components/componentcore/utils3d.h index b14784d0291..38a47f465dc 100644 --- a/src/plugins/qmldesigner/components/componentcore/utils3d.h +++ b/src/plugins/qmldesigner/components/componentcore/utils3d.h @@ -43,6 +43,8 @@ ModelNode selectedTexture(AbstractView *view); ModelNode resolveSceneEnv(AbstractView *view, int sceneId); QList getSelectedModels(AbstractView *view); +QList getSelectedTextures(AbstractView *view); +QList getSelectedMaterials(AbstractView *view); void applyMaterialToModels(AbstractView *view, const ModelNode &material, const QList &models, bool add = false); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp index 1c8d15d08a7..5ee5e7a1c6b 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp @@ -33,32 +33,32 @@ int MaterialBrowserModel::rowCount(const QModelIndex &) const QVariant MaterialBrowserModel::data(const QModelIndex &index, int role) const { - QTC_ASSERT(index.isValid() && index.row() < m_materialList.size(), return {}); - QTC_ASSERT(roleNames().contains(role), return {}); + QTC_ASSERT(index.isValid(), return {}); - QByteArray roleName = roleNames().value(role); - if (roleName == "materialName") { + switch (role) { + case Roles::NameRole: { QVariant objName = m_materialList.at(index.row()).variantProperty("objectName").value(); return objName.isValid() ? objName : ""; - } - - if (roleName == "materialInternalId") + } break; + case Roles::InternalIdRole: return m_materialList.at(index.row()).internalId(); - - if (roleName == "materialVisible") + case Roles::MatchedSearchRole: return isVisible(index.row()); - - if (roleName == "materialType") { + case Roles::SelectedRole: + return m_materialList.at(index.row()).isSelected(); + case Roles::IsComponentRole: + return m_materialList.at(index.row()).isComponent(); + case Roles::TypeRole: { QString matType = QString::fromLatin1(m_materialList.at(index.row()).type()); if (matType.startsWith("QtQuick3D.")) matType.remove("QtQuick3D."); return matType; - } - - if (roleName == "hasDynamicProperties") + } break; + case Roles::HasDynamicPropertiesRole: return !m_materialList.at(index.row()).dynamicProperties().isEmpty(); - - return {}; + default: + return {}; + }; } bool MaterialBrowserModel::isVisible(int idx) const @@ -137,12 +137,14 @@ void MaterialBrowserModel::unloadPropertyGroups() QHash MaterialBrowserModel::roleNames() const { - static const QHash roles { - {Qt::UserRole + 1, "materialName"}, - {Qt::UserRole + 2, "materialInternalId"}, - {Qt::UserRole + 3, "materialVisible"}, - {Qt::UserRole + 4, "materialType"}, - {Qt::UserRole + 5, "hasDynamicProperties"} + static const QHash roles{ + {Roles::NameRole, "materialName"}, + {Roles::InternalIdRole, "materialInternalId"}, + {Roles::MatchedSearchRole, "materialMatchedSearch"}, + {Roles::SelectedRole, "materialSelected"}, + {Roles::IsComponentRole, "materialIsComponent"}, + {Roles::TypeRole, "materialType"}, + {Roles::HasDynamicPropertiesRole, "hasDynamicProperties"}, }; return roles; } @@ -236,26 +238,13 @@ void MaterialBrowserModel::setSearchText(const QString &searchText) void MaterialBrowserModel::refreshSearch() { - bool isEmpty = false; + bool isEmpty = true; - // if selected material goes invisible, select nearest material - if (!isVisible(m_selectedIndex)) { - int inc = 1; - int incCap = m_materialList.size(); - while (!isEmpty && inc < incCap) { - if (isVisible(m_selectedIndex - inc)) { - selectMaterial(m_selectedIndex - inc); - break; - } else if (isVisible(m_selectedIndex + inc)) { - selectMaterial(m_selectedIndex + inc); - break; - } - ++inc; - isEmpty = !isValidIndex(m_selectedIndex + inc) - && !isValidIndex(m_selectedIndex - inc); + for (int i = 0; i < m_materialList.size(); ++i) { + if (isVisible(i)) { + isEmpty = false; + break; } - if (!isVisible(m_selectedIndex)) // handles the case of a single material - isEmpty = true; } if (isEmpty != m_isEmpty) { @@ -284,7 +273,6 @@ void MaterialBrowserModel::setMaterials(const QList &materials, bool else resetModel(); - updateSelectedMaterial(); setHasQuick3DImport(hasQuick3DImport); } @@ -309,19 +297,18 @@ void MaterialBrowserModel::removeMaterial(const ModelNode &material) } } -void MaterialBrowserModel::deleteSelectedMaterial() +void MaterialBrowserModel::deleteSelectedMaterials() { - deleteMaterial(m_selectedIndex); -} + m_view->executeInTransaction(__FUNCTION__, [this] { + QStack selectedIndexes; + for (int i = 0; i < m_materialList.size(); ++i) { + if (m_materialList.at(i).isSelected()) + selectedIndexes << i; + } -void MaterialBrowserModel::updateSelectedMaterial() -{ - if (!m_materialList.isEmpty() && m_selectedIndex < 0) { - ModelNode mat = Utils3D::selectedMaterial(m_view); - m_selectedIndex = materialIndex(mat); - } - - selectMaterial(m_selectedIndex, true); + while (!selectedIndexes.isEmpty()) + deleteMaterial(selectedIndexes.pop()); + }); } void MaterialBrowserModel::updateMaterialName(const ModelNode &material) @@ -344,36 +331,51 @@ ModelNode MaterialBrowserModel::materialAt(int idx) const return {}; } -ModelNode MaterialBrowserModel::selectedMaterial() const -{ - if (isValidIndex(m_selectedIndex)) - return m_materialList[m_selectedIndex]; - return {}; -} - void MaterialBrowserModel::resetModel() { beginResetModel(); endResetModel(); } -void MaterialBrowserModel::selectMaterial(int idx, bool force) +void MaterialBrowserModel::notifySelectionChanges(const QList &selectedNodes, + const QList &deselectedNodes) { - if (m_materialList.size() == 0) { - m_selectedIndex = -1; - emit selectedIndexChanged(m_selectedIndex); + QList indices; + indices.reserve(selectedNodes.size() + deselectedNodes.size()); + for (const ModelNode &node : selectedNodes) + indices.append(materialIndex(node)); + + for (const ModelNode &node : deselectedNodes) + indices.append(materialIndex(node)); + + using Bound = QPair; + const QList &bounds = MaterialBrowserView::getSortedBounds(indices); + + for (const Bound &bound : bounds) + emit dataChanged(index(bound.first), index(bound.second), {Roles::SelectedRole}); +} + +void MaterialBrowserModel::updateMaterialComponent(int idx) +{ + if (!isValidIndex(idx)) return; - } - idx = std::max(0, std::min(idx, rowCount() - 1)); + const QModelIndex &mIdx = index(idx); + emit dataChanged(mIdx, mIdx, {Roles::IsComponentRole}); +} - if (idx != m_selectedIndex || force) { - m_selectedIndex = idx; - emit selectedIndexChanged(idx); +void MaterialBrowserModel::selectMaterial(int idx, bool appendMat) +{ + if (!isValidIndex(idx)) + return; - m_selectedMaterialIsComponent = selectedMaterial().isComponent(); - emit selectedMaterialIsComponentChanged(); - } + ModelNode mat = m_materialList.at(idx); + QTC_ASSERT(mat, return); + + if (appendMat) + mat.view()->selectModelNode(mat); + else + mat.selectNode(); } void MaterialBrowserModel::duplicateMaterial(int idx) diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h index 3b6b64ec865..1d5df5673f2 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h @@ -19,8 +19,6 @@ class MaterialBrowserModel : public QAbstractListModel Q_OBJECT Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) - Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) - Q_PROPERTY(bool selectedMaterialIsComponent MEMBER m_selectedMaterialIsComponent NOTIFY selectedMaterialIsComponentChanged) Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged) Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary WRITE setHasMaterialLibrary NOTIFY hasMaterialLibraryChanged) @@ -62,18 +60,19 @@ public: QList materials() const; void setMaterials(const QList &materials, bool hasQuick3DImport); void removeMaterial(const ModelNode &material); - void deleteSelectedMaterial(); + void deleteSelectedMaterials(); void updateMaterialName(const ModelNode &material); - void updateSelectedMaterial(); int materialIndex(const ModelNode &material) const; ModelNode materialAt(int idx) const; - ModelNode selectedMaterial() const; bool loadPropertyGroups(const QString &path); void unloadPropertyGroups(); void resetModel(); + void notifySelectionChanges(const QList &selectedNodes, + const QList &deselectedNodes); + void updateMaterialComponent(int idx); - Q_INVOKABLE void selectMaterial(int idx, bool force = false); + Q_INVOKABLE void selectMaterial(int idx, bool appendMat = false); Q_INVOKABLE void duplicateMaterial(int idx); Q_INVOKABLE void copyMaterialProperties(int idx, const QString §ion); Q_INVOKABLE void pasteMaterialProperties(int idx); @@ -101,7 +100,6 @@ signals: void hasMaterialLibraryChanged(); void copiedMaterialTypeChanged(); void materialSectionsChanged(); - void selectedIndexChanged(int idx); void renameMaterialTriggered(const QmlDesigner::ModelNode &material, const QString &newName); void applyToSelectedTriggered(const QmlDesigner::ModelNode &material, bool add = false); void addNewMaterialTriggered(); @@ -111,11 +109,20 @@ signals: const QList &props, bool all); void isQt6ProjectChanged(); - void selectedMaterialIsComponentChanged(); private: bool isValidIndex(int idx) const; + enum Roles { + NameRole = Qt::UserRole + 1, + InternalIdRole, + MatchedSearchRole, + SelectedRole, + IsComponentRole, + TypeRole, + HasDynamicPropertiesRole, + }; + QString m_searchText; QList m_materialList; QStringList m_defaultMaterialSections; @@ -127,14 +134,12 @@ private: QHash m_materialIndexHash; // internalId -> index QJsonObject m_propertyGroupsObj; - int m_selectedIndex = -1; bool m_isEmpty = true; bool m_hasQuick3DImport = false; bool m_hasModelSelection = false; bool m_hasMaterialLibrary = false; bool m_allPropsCopied = true; bool m_isQt6Project = false; - bool m_selectedMaterialIsComponent = false; QString m_copiedMaterialType; QPointer m_view; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index 1c56ab927aa..b03ac632b4e 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -16,6 +16,11 @@ namespace QmlDesigner { +static bool isMaterial(const ModelNode &node) +{ + return node.metaInfo().isQtQuick3DMaterial(); +} + MaterialBrowserTexturesModel::MaterialBrowserTexturesModel(MaterialBrowserView *view, QObject *parent) : QAbstractListModel(parent) , m_view(view) @@ -37,8 +42,10 @@ QVariant MaterialBrowserTexturesModel::data(const QModelIndex &index, int role) QTC_ASSERT(roleNames().contains(role), return {}); switch (role) { - case RoleTexVisible: + case RoleMatchedSearch: return isVisible(index.row()); + case RoleTexSelected: + return m_textureList.at(index.row()).isSelected(); case RoleTexHasDynamicProps: return !m_textureList.at(index.row()).dynamicProperties().isEmpty(); case RoleTexInternalId: @@ -102,6 +109,15 @@ bool MaterialBrowserTexturesModel::isValidIndex(int idx) const return idx > -1 && idx < rowCount(); } +void MaterialBrowserTexturesModel::setOnlyMaterialsSelected(bool value) +{ + if (m_onlyMaterialsSelected == value) + return; + + m_onlyMaterialsSelected = value; + emit onlyMaterialsSelectedChanged(); +} + QHash MaterialBrowserTexturesModel::roleNames() const { static const QHash roles{ @@ -110,7 +126,8 @@ QHash MaterialBrowserTexturesModel::roleNames() const {RoleTexName, "textureName"}, {RoleTexSource, "textureSource"}, {RoleTexToolTip, "textureToolTip"}, - {RoleTexVisible, "textureVisible"}, + {RoleMatchedSearch, "textureMatchedSearch"}, + {RoleTexSelected, "textureSelected"}, }; return roles; } @@ -134,26 +151,13 @@ void MaterialBrowserTexturesModel::setSearchText(const QString &searchText) void MaterialBrowserTexturesModel::refreshSearch() { - bool isEmpty = false; + bool isEmpty = true; - // if selected texture goes invisible, select nearest one - if (!isVisible(m_selectedIndex)) { - int inc = 1; - int incCap = m_textureList.size(); - while (!isEmpty && inc < incCap) { - if (isVisible(m_selectedIndex - inc)) { - selectTexture(m_selectedIndex - inc); - break; - } else if (isVisible(m_selectedIndex + inc)) { - selectTexture(m_selectedIndex + inc); - break; - } - ++inc; - isEmpty = !isValidIndex(m_selectedIndex + inc) - && !isValidIndex(m_selectedIndex - inc); + for (int i = 0; i < m_textureList.size(); ++i) { + if (isVisible(i)) { + isEmpty = false; + break; } - if (!isVisible(m_selectedIndex)) // handles the case of a single item - isEmpty = true; } if (isEmpty != m_isEmpty) { @@ -177,7 +181,6 @@ void MaterialBrowserTexturesModel::setTextures(const QList &textures) emit isEmptyChanged(); } - updateSelectedTexture(); resetModel(); } @@ -207,9 +210,18 @@ void MaterialBrowserTexturesModel::addNewTexture() emit addNewTextureTriggered(); } -void MaterialBrowserTexturesModel::deleteSelectedTexture() +void MaterialBrowserTexturesModel::deleteSelectedTextures() { - deleteTexture(m_selectedIndex); + m_view->executeInTransaction(__FUNCTION__, [this] { + QStack selectedIndexes; + for (int i = 0; i < m_textureList.size(); ++i) { + if (m_textureList.at(i).isSelected()) + selectedIndexes << i; + } + + while (!selectedIndexes.isEmpty()) + deleteTexture(selectedIndexes.pop()); + }); } void MaterialBrowserTexturesModel::updateTextureSource(const ModelNode &texture) @@ -238,14 +250,22 @@ void MaterialBrowserTexturesModel::updateAllTexturesSources() emit dataChanged(index(0, 0), index(rowCount() - 1, 0), {RoleTexSource, RoleTexToolTip}); } -void MaterialBrowserTexturesModel::updateSelectedTexture() +void MaterialBrowserTexturesModel::notifySelectionChanges(const QList &selectedNodes, + const QList &deselectedNodes) { - if (!m_textureList.isEmpty() && m_selectedIndex < 0) { - ModelNode tex = Utils3D::selectedTexture(m_view); - m_selectedIndex = textureIndex(tex); - } + QList indices; + indices.reserve(selectedNodes.size() + deselectedNodes.size()); + for (const ModelNode &node : selectedNodes) + indices.append(textureIndex(node)); - selectTexture(m_selectedIndex, true); + for (const ModelNode &node : deselectedNodes) + indices.append(textureIndex(node)); + + using Bound = QPair; + const QList &bounds = MaterialBrowserView::getSortedBounds(indices); + + for (const Bound &bound : bounds) + emit dataChanged(index(bound.first), index(bound.second), {Roles::RoleTexSelected}); } int MaterialBrowserTexturesModel::textureIndex(const ModelNode &texture) const @@ -261,11 +281,6 @@ ModelNode MaterialBrowserTexturesModel::textureAt(int idx) const return {}; } -ModelNode MaterialBrowserTexturesModel::selectedTexture() const -{ - return textureAt(m_selectedIndex); -} - bool MaterialBrowserTexturesModel::hasSingleModelSelection() const { return m_hasSingleModelSelection; @@ -280,6 +295,11 @@ void MaterialBrowserTexturesModel::setHasSingleModelSelection(bool b) emit hasSingleModelSelectionChanged(); } +bool MaterialBrowserTexturesModel::onlyMaterialsSelected() const +{ + return m_onlyMaterialsSelected; +} + bool MaterialBrowserTexturesModel::hasSceneEnv() const { return m_hasSceneEnv; @@ -300,20 +320,18 @@ void MaterialBrowserTexturesModel::resetModel() endResetModel(); } -void MaterialBrowserTexturesModel::selectTexture(int idx, bool force) +void MaterialBrowserTexturesModel::selectTexture(int idx, bool appendTxt) { - if (m_textureList.size() == 0) { - m_selectedIndex = -1; - emit selectedIndexChanged(m_selectedIndex); + if (!isValidIndex(idx)) return; - } - idx = std::max(0, std::min(idx, rowCount() - 1)); + ModelNode texture = m_textureList.at(idx); + QTC_ASSERT(texture, return); - if (idx != m_selectedIndex || force) { - m_selectedIndex = idx; - emit selectedIndexChanged(idx); - } + if (appendTxt) + texture.view()->selectModelNode(texture); + else + texture.selectNode(); } void MaterialBrowserTexturesModel::duplicateTexture(int idx) @@ -369,6 +387,15 @@ void MaterialBrowserTexturesModel::updateSceneEnvState() emit updateSceneEnvStateRequested(); } +void MaterialBrowserTexturesModel::updateSelectionState() +{ + setHasSingleModelSelection( + m_view->hasSingleSelectedModelNode() + && Utils3D::getMaterialOfModel(m_view->singleSelectedModelNode()).isValid()); + + setOnlyMaterialsSelected(Utils::allOf(m_view->selectedModelNodes(), isMaterial)); +} + void MaterialBrowserTexturesModel::applyAsLightProbe(qint64 internalId) { int idx = m_textureIndexHash.value(internalId); @@ -378,9 +405,4 @@ void MaterialBrowserTexturesModel::applyAsLightProbe(qint64 internalId) } } -void MaterialBrowserTexturesModel::updateModelSelectionState() -{ - emit updateModelSelectionStateRequested(); -} - } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h index 5625cc7d578..89d338ee9d1 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.h @@ -17,8 +17,8 @@ class MaterialBrowserTexturesModel : public QAbstractListModel Q_OBJECT Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) - Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) Q_PROPERTY(bool hasSingleModelSelection READ hasSingleModelSelection NOTIFY hasSingleModelSelectionChanged) + Q_PROPERTY(bool onlyMaterialsSelected READ onlyMaterialsSelected NOTIFY onlyMaterialsSelectedChanged) Q_PROPERTY(bool hasSceneEnv READ hasSceneEnv NOTIFY hasSceneEnvChanged) public: @@ -35,19 +35,20 @@ public: QList textures() const; void setTextures(const QList &textures); void removeTexture(const ModelNode &texture); - void deleteSelectedTexture(); - void updateSelectedTexture(); + void deleteSelectedTextures(); void updateTextureSource(const ModelNode &texture); void updateTextureId(const ModelNode &texture); void updateTextureName(const ModelNode &texture); void updateAllTexturesSources(); + void notifySelectionChanges(const QList &selectedNodes, + const QList &deselectedNodes); int textureIndex(const ModelNode &texture) const; ModelNode textureAt(int idx) const; - ModelNode selectedTexture() const; bool hasSingleModelSelection() const; void setHasSingleModelSelection(bool b); + bool onlyMaterialsSelected() const; bool hasSceneEnv() const; void setHasSceneEnv(bool b); @@ -55,7 +56,7 @@ public: void resetModel(); - Q_INVOKABLE void selectTexture(int idx, bool force = false); + Q_INVOKABLE void selectTexture(int idx, bool appendTxt = false); Q_INVOKABLE void addNewTexture(); Q_INVOKABLE void duplicateTexture(int idx); Q_INVOKABLE void deleteTexture(int idx); @@ -64,44 +65,45 @@ public: Q_INVOKABLE void applyToSelectedModel(qint64 internalId); Q_INVOKABLE void openTextureEditor(); Q_INVOKABLE void updateSceneEnvState(); - Q_INVOKABLE void updateModelSelectionState(); + Q_INVOKABLE void updateSelectionState(); Q_INVOKABLE void applyAsLightProbe(qint64 internalId); Q_INVOKABLE bool isVisible(int idx) const; signals: void isEmptyChanged(); void hasSingleModelSelectionChanged(); - void selectedIndexChanged(int idx); + void onlyMaterialsSelectedChanged(); void duplicateTextureTriggered(const QmlDesigner::ModelNode &texture); void applyToSelectedMaterialTriggered(const QmlDesigner::ModelNode &texture); void applyToSelectedModelTriggered(const QmlDesigner::ModelNode &texture); void addNewTextureTriggered(); void updateSceneEnvStateRequested(); - void updateModelSelectionStateRequested(); void hasSceneEnvChanged(); void applyAsLightProbeRequested(const QmlDesigner::ModelNode &texture); private: bool isValidIndex(int idx) const; + void setOnlyMaterialsSelected(bool value); QString m_searchText; QList m_textureList; QHash m_textureIndexHash; // internalId -> index - int m_selectedIndex = 0; bool m_isEmpty = true; bool m_hasSingleModelSelection = false; + bool m_onlyMaterialsSelected = false; bool m_hasSceneEnv = false; QPointer m_view; - enum { + enum Roles { RoleTexHasDynamicProps = Qt::UserRole + 1, RoleTexInternalId, RoleTexName, RoleTexSource, RoleTexToolTip, - RoleTexVisible + RoleMatchedSearch, + RoleTexSelected, }; }; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 4335b09c482..eea891ceec9 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -36,6 +36,16 @@ namespace QmlDesigner { +static bool isMaterial(const ModelNode &node) +{ + return node.metaInfo().isQtQuick3DMaterial(); +} + +static bool isTexture(const ModelNode &node) +{ + return node.metaInfo().isQtQuick3DTexture(); +} + static QString propertyEditorResourcesPath() { #ifdef SHARE_QML_PATH @@ -52,9 +62,6 @@ MaterialBrowserView::MaterialBrowserView(AsynchronousImageCache &imageCache, { m_previewTimer.setSingleShot(true); connect(&m_previewTimer, &QTimer::timeout, this, &MaterialBrowserView::requestPreviews); - - m_selectionTimer.setSingleShot(true); - connect(&m_selectionTimer, &QTimer::timeout, this, &MaterialBrowserView::handleModelSelectionChange); } MaterialBrowserView::~MaterialBrowserView() @@ -73,13 +80,6 @@ WidgetInfo MaterialBrowserView::widgetInfo() // custom notifications below are sent to the MaterialEditor MaterialBrowserModel *matBrowserModel = m_widget->materialBrowserModel().data(); - connect(matBrowserModel, &MaterialBrowserModel::selectedIndexChanged, this, [this](int idx) { - if (!model()) - return; - m_pendingMaterialIndex = idx; - m_selectionTimer.start(0); - }); - connect(matBrowserModel, &MaterialBrowserModel::applyToSelectedTriggered, this, [&] (const ModelNode &material, bool add) { Utils3D::applyMaterialToModels(this, material, Utils3D::getSelectedModels(this), add); @@ -174,12 +174,6 @@ WidgetInfo MaterialBrowserView::widgetInfo() // custom notifications below are sent to the TextureEditor MaterialBrowserTexturesModel *texturesModel = m_widget->materialBrowserTexturesModel().data(); - connect(texturesModel, &MaterialBrowserTexturesModel::selectedIndexChanged, this, [this](int idx) { - if (!model()) - return; - m_pendingTextureIndex = idx; - m_selectionTimer.start(0); - }); connect(texturesModel, &MaterialBrowserTexturesModel::duplicateTextureTriggered, this, [&] (const ModelNode &texture) { QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("TextureEditor"); @@ -190,8 +184,9 @@ WidgetInfo MaterialBrowserView::widgetInfo() [&] (const ModelNode &texture) { if (!m_widget) return; - const ModelNode material = m_widget->materialBrowserModel()->selectedMaterial(); - applyTextureToMaterial({material}, texture); + + ModelNodes materialNodes = Utils3D::getSelectedMaterials(this); + applyTextureToMaterial(materialNodes, texture); }); connect(texturesModel, &MaterialBrowserTexturesModel::applyToSelectedModelTriggered, this, @@ -214,14 +209,6 @@ WidgetInfo MaterialBrowserView::widgetInfo() m_widget->materialBrowserTexturesModel()->setHasSceneEnv(sceneEnvExists); }); - connect(texturesModel, &MaterialBrowserTexturesModel::updateModelSelectionStateRequested, this, [this] { - bool hasModel = false; - const QList selectedModels = Utils3D::getSelectedModels(this); - if (selectedModels.size() == 1) - hasModel = Utils3D::getMaterialOfModel(selectedModels.at(0)).isValid(); - m_widget->materialBrowserTexturesModel()->setHasSingleModelSelection(hasModel); - }); - connect(texturesModel, &MaterialBrowserTexturesModel::applyAsLightProbeRequested, this, @@ -290,9 +277,6 @@ void MaterialBrowserView::refreshModel(bool updateImages) if (updateImages) updateMaterialsPreview(); - - updateMaterialSelection(); - updateTextureSelection(); } void MaterialBrowserView::updateMaterialsPreview() @@ -315,13 +299,12 @@ void MaterialBrowserView::updatePropertyList(const QList &propertyList) else m_previewRequests << node; } else if (isTexture(node)) { - QmlObjectNode selectedTex = m_widget->materialBrowserTexturesModel()->selectedTexture(); if (property.name() == "source") m_widget->materialBrowserTexturesModel()->updateTextureSource(node); else if (property.name() == "objectName") m_widget->materialBrowserTexturesModel()->updateTextureName(node); } else { - QmlObjectNode selectedTex = m_widget->materialBrowserTexturesModel()->selectedTexture(); + QmlObjectNode selectedTex = Utils3D::selectedTexture(this); if (property.name() == "source" && selectedTex.propertyChangeForCurrentState() == node) m_widget->materialBrowserTexturesModel()->updateTextureSource(selectedTex); } @@ -331,26 +314,11 @@ void MaterialBrowserView::updatePropertyList(const QList &propertyList) m_previewTimer.start(0); } -bool MaterialBrowserView::isMaterial(const ModelNode &node) const -{ - return node.metaInfo().isQtQuick3DMaterial(); -} - -bool MaterialBrowserView::isTexture(const ModelNode &node) const -{ - if (!node.isValid()) - return false; - - return node.metaInfo().isQtQuick3DTexture(); -} - void MaterialBrowserView::modelAboutToBeDetached(Model *model) { m_widget->materialBrowserModel()->setMaterials({}, m_hasQuick3DImport); m_widget->materialBrowserModel()->setHasMaterialLibrary(false); m_widget->clearPreviewCache(); - m_pendingMaterialIndex = -1; - m_pendingTextureIndex = -1; if (m_propertyGroupsLoaded) { m_propertyGroupsLoaded = false; @@ -363,25 +331,19 @@ void MaterialBrowserView::modelAboutToBeDetached(Model *model) void MaterialBrowserView::selectedNodesChanged([[maybe_unused]] const QList &selectedNodeList, [[maybe_unused]] const QList &lastSelectedNodeList) { - const QList selectedModels = Utils3D::getSelectedModels(this); + using namespace std::ranges; - m_widget->materialBrowserModel()->setHasModelSelection(!selectedModels.isEmpty()); + ModelNodes selectedMaterials = Utils::filtered(selectedNodeList, isMaterial); + ModelNodes deselectedMaterials = Utils::filtered(lastSelectedNodeList, isMaterial); - // the logic below selects the material of the first selected model if auto selection is on - if (!m_autoSelectModelMaterial) - return; + ModelNodes selectedTextures = Utils::filtered(selectedNodeList, isTexture); + ModelNodes deselectedTextures = Utils::filtered(lastSelectedNodeList, isTexture); - if (selectedNodeList.size() > 1 || selectedModels.isEmpty()) - return; + m_widget->materialBrowserModel()->notifySelectionChanges(selectedMaterials, deselectedMaterials); + m_widget->materialBrowserModel()->setHasModelSelection(!selectedMaterials.isEmpty()); - ModelNode mat = Utils3D::getMaterialOfModel(selectedModels.at(0)); - - if (!mat.isValid()) - return; - - // if selected object is a model, select its material in the material browser and editor - int idx = m_widget->materialBrowserModel()->materialIndex(mat); - m_widget->materialBrowserModel()->selectMaterial(idx); + m_widget->materialBrowserTexturesModel()->notifySelectionChanges(selectedTextures, + deselectedTextures); } void MaterialBrowserView::modelNodePreviewPixmapChanged(const ModelNode &node, @@ -450,12 +412,8 @@ void MaterialBrowserView::nodeReparented(const ModelNode &node, resetPuppet(); m_puppetResetPending = true; } - int idx = m_widget->materialBrowserModel()->materialIndex(node); - m_widget->materialBrowserModel()->selectMaterial(idx); m_widget->materialBrowserModel()->refreshSearch(); } else { // is texture - int idx = m_widget->materialBrowserTexturesModel()->textureIndex(node); - m_widget->materialBrowserTexturesModel()->selectTexture(idx); m_widget->materialBrowserTexturesModel()->refreshSearch(); } } @@ -480,17 +438,6 @@ void MaterialBrowserView::nodeAboutToBeRemoved(const ModelNode &removedNode) m_widget->materialBrowserTexturesModel()->removeTexture(removedNode); } -void MaterialBrowserView::nodeRemoved([[maybe_unused]] const ModelNode &removedNode, - const NodeAbstractProperty &parentProperty, - [[maybe_unused]] PropertyChangeFlags propertyChange) -{ - if (parentProperty.parentModelNode().id() != Constants::MATERIAL_LIB_ID) - return; - - m_widget->materialBrowserModel()->updateSelectedMaterial(); - m_widget->materialBrowserTexturesModel()->updateSelectedTexture(); -} - void QmlDesigner::MaterialBrowserView::loadPropertyGroups() { if (!m_hasQuick3DImport || m_propertyGroupsLoaded || !model()) @@ -515,59 +462,6 @@ void MaterialBrowserView::requestPreviews() m_previewRequests.clear(); } -void MaterialBrowserView::updateMaterialSelection() -{ - QTC_ASSERT(model(), return); - - ModelNode node = Utils3D::selectedMaterial(this); - int idx = m_widget->materialBrowserModel()->materialIndex(node); - if (idx == -1 && !m_widget->materialBrowserModel()->isEmpty()) - idx = 0; - if (idx != -1) { - m_widget->materialBrowserModel()->selectMaterial(idx); - m_widget->focusMaterialSection(true); - } -} - -void MaterialBrowserView::updateTextureSelection() -{ - QTC_ASSERT(model(), return); - - ModelNode node = Utils3D::selectedTexture(this); - int idx = m_widget->materialBrowserTexturesModel()->textureIndex(node); - if (idx == -1 && !m_widget->materialBrowserTexturesModel()->isEmpty()) - idx = 0; - if (idx != -1) { - m_widget->materialBrowserTexturesModel()->selectTexture(idx); - m_widget->materialBrowserTexturesModel()->refreshSearch(); - m_widget->focusMaterialSection(false); - } -} - -// This method is asynchronously called in response to a selection change in the material and -// texture models to update the selection state to the main scene model. -void MaterialBrowserView::handleModelSelectionChange() -{ - if (!model()) - return; - - if (m_pendingMaterialIndex >= 0) { - ModelNode matNode = m_widget->materialBrowserModel()->materialAt(m_pendingMaterialIndex); - ModelNode current = Utils3D::selectedMaterial(this); - if (current != matNode) - Utils3D::selectMaterial(matNode); - m_pendingMaterialIndex = -1; - } - - if (m_pendingTextureIndex >= 0) { - ModelNode texNode = m_widget->materialBrowserTexturesModel()->textureAt(m_pendingTextureIndex); - ModelNode current = Utils3D::selectedTexture(this); - if (current != texNode) - Utils3D::selectTexture(texNode); - m_pendingTextureIndex = -1; - } -} - void MaterialBrowserView::importsChanged([[maybe_unused]] const Imports &addedImports, [[maybe_unused]] const Imports &removedImports) { @@ -597,7 +491,7 @@ void MaterialBrowserView::customNotification(const AbstractView *view, refreshModel(true); }); } else if (identifier == "delete_selected_material") { - m_widget->deleteSelectedItem(); + m_widget->deleteSelectedItems(); } else if (identifier == "apply_asset_to_model3D") { m_appliedTexturePath = data.at(0).toString(); applyTextureToModel3D(nodeList.at(0)); @@ -643,7 +537,7 @@ void MaterialBrowserView::instancePropertyChanged(const QListclose(); } +/*! + * \internal + * \brief Gets a list of subranges which covers the input list + * Each subrange will be extended until reaches a gap. + * A gap is defined as a range that is not included in the input list. + * Minimum length of the gap should be 2, since 1 is considered as a + * continuous range. + * \param values: unsorted integer list + * \return A sorted list of closed subranges. Each pair consists of two + * numbers. The first number is the start of the subrange, and the second + * number is the end of subrange which is available in the values. + */ +QList> MaterialBrowserView::getSortedBounds(const QList &values) +{ + using Bound = QPair; + QList sortedValues = Utils::sorted(values); + + Bound tempBound; + QList bounds; + bounds.reserve(sortedValues.size()); + + if (!sortedValues.isEmpty()) { + tempBound.first = sortedValues.first(); + tempBound.second = sortedValues.first(); + } + + for (int value : std::as_const(sortedValues)) { + // If the difference is more than 1, a gap is found. + // We need to close the previous subrange, and start a new one + if (value - tempBound.second > 1) { + bounds << tempBound; + tempBound.first = value; + } + tempBound.second = value; + } + + if (!sortedValues.isEmpty()) + bounds << tempBound; + + bounds.shrink_to_fit(); + return bounds; +} + bool MaterialBrowserView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h index c14bf1ade3a..3ae62c9e60c 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -47,8 +47,6 @@ public: const NodeAbstractProperty &oldPropertyParent, AbstractView::PropertyChangeFlags propertyChange) override; void nodeAboutToBeRemoved(const ModelNode &removedNode) override; - void nodeRemoved(const ModelNode &removedNode, const NodeAbstractProperty &parentProperty, - PropertyChangeFlags propertyChange) override; void importsChanged(const Imports &addedImports, const Imports &removedImports) override; void customNotification(const AbstractView *view, const QString &identifier, const QList &nodeList, const QList &data) override; @@ -68,6 +66,8 @@ public: Q_INVOKABLE void applyTextureToProperty(const QString &matId, const QString &propName); Q_INVOKABLE void closeChooseMatPropsView(); + static QList> getSortedBounds(const QList &values); + protected: bool eventFilter(QObject *obj, QEvent *event) override; @@ -79,33 +79,24 @@ private: template::value>::type> void updatePropertyList(const QList &propertyList); - bool isMaterial(const ModelNode &node) const; - bool isTexture(const ModelNode &node) const; void loadPropertyGroups(); void requestPreviews(); ModelNode resolveSceneEnv(); - void updateMaterialSelection(); - void updateTextureSelection(); - void handleModelSelectionChange(); AsynchronousImageCache &m_imageCache; QPointer m_widget; bool m_hasQuick3DImport = false; - bool m_autoSelectModelMaterial = false; // TODO: wire this to some action bool m_puppetResetPending = false; bool m_propertyGroupsLoaded = false; QTimer m_previewTimer; - QTimer m_selectionTimer; // Compress selection and avoid illegal callbacks to model QSet m_previewRequests; QPointer m_chooseMatPropsView; QHash> m_textureModels; QString m_appliedTextureId; QString m_appliedTexturePath; // defers texture creation until dialog apply int m_sceneId = -1; - int m_pendingMaterialIndex = -1; - int m_pendingTextureIndex = -1; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index a8b29b52b41..7a0529ed5d1 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -237,12 +237,12 @@ void MaterialBrowserWidget::updateMaterialPreview(const ModelNode &node, const Q QMetaObject::invokeMethod(m_quickWidget->rootObject(), "refreshPreview", Q_ARG(QVariant, idx)); } -void MaterialBrowserWidget::deleteSelectedItem() +void MaterialBrowserWidget::deleteSelectedItems() { - if (m_materialSectionFocused) - m_materialBrowserModel->deleteSelectedMaterial(); - else - m_materialBrowserTexturesModel->deleteSelectedTexture(); + m_materialBrowserView->executeInTransaction(__FUNCTION__, [this] { + m_materialBrowserModel->deleteSelectedMaterials(); + m_materialBrowserTexturesModel->deleteSelectedTextures(); + }); } QList MaterialBrowserWidget::createToolBarWidgets() @@ -312,7 +312,7 @@ void MaterialBrowserWidget::acceptBundleTextureDropOnMaterial(int matIndex, cons ModelNode tex = CreateTexture(m_materialBrowserView).execute(bundleTexPath.toLocalFile()); QTC_ASSERT(tex.isValid(), return); - m_materialBrowserModel->selectMaterial(matIndex); + mat.model()->setSelectedModelNodes({mat}); m_materialBrowserView->applyTextureToMaterial({mat}, tex); }); @@ -341,7 +341,7 @@ void MaterialBrowserWidget::acceptAssetsDropOnMaterial(int matIndex, const QList ModelNode tex = CreateTexture(m_materialBrowserView).execute(imageSrc); QTC_ASSERT(tex.isValid(), return); - m_materialBrowserModel->selectMaterial(matIndex); + mat.model()->setSelectedModelNodes({mat}); m_materialBrowserView->applyTextureToMaterial({mat}, tex); }); @@ -355,7 +355,7 @@ void MaterialBrowserWidget::acceptTextureDropOnMaterial(int matIndex, const QStr ModelNode tex = m_materialBrowserView->modelNodeForInternalId(texId.toInt()); if (mat.isValid() && tex.isValid()) { - m_materialBrowserModel->selectMaterial(matIndex); + mat.model()->setSelectedModelNodes({mat}); m_materialBrowserView->applyTextureToMaterial({mat}, tex); } @@ -371,10 +371,10 @@ void MaterialBrowserWidget::focusMaterialSection(bool focusMatSec) } } -void MaterialBrowserWidget::addMaterialToContentLibrary() +void MaterialBrowserWidget::addMaterialToContentLibrary(const QVariant &material) { QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("ContentLibrary"); - ModelNode mat = m_materialBrowserModel->selectedMaterial(); + ModelNode mat = material.value(); m_materialBrowserView->emitCustomNotification("add_material_to_content_lib", {mat}, {m_previewImageProvider->getPixmap(mat)}); // to ContentLibrary } @@ -384,10 +384,13 @@ void MaterialBrowserWidget::importMaterial() m_bundleHelper->importBundleToProject(); } -void MaterialBrowserWidget::exportMaterial() +void MaterialBrowserWidget::exportMaterial(int idx) { - ModelNode mat = m_materialBrowserModel->selectedMaterial(); + ModelNode mat = m_materialBrowserModel->materialAt(idx); + QTC_ASSERT(mat, return); + m_bundleHelper->exportBundle({mat}, m_previewImageProvider->getPixmap(mat)); + m_materialBrowserModel->updateMaterialComponent(idx); } void MaterialBrowserWidget::addQtQuick3D() diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h index 647847cb276..85238149510 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h @@ -50,7 +50,7 @@ public: QPointer materialBrowserModel() const; QPointer materialBrowserTexturesModel() const; void updateMaterialPreview(const ModelNode &node, const QPixmap &pixmap); - void deleteSelectedItem(); + void deleteSelectedItems(); Q_INVOKABLE void handleSearchFilterChanged(const QString &filterText); Q_INVOKABLE void startDragMaterial(int index, const QPointF &mousePos); @@ -63,9 +63,9 @@ public: Q_INVOKABLE void acceptAssetsDropOnMaterial(int matIndex, const QList &urls); Q_INVOKABLE void acceptTextureDropOnMaterial(int matIndex, const QString &texId); Q_INVOKABLE void focusMaterialSection(bool focusMatSec); - Q_INVOKABLE void addMaterialToContentLibrary(); + Q_INVOKABLE void addMaterialToContentLibrary(const QVariant &material); Q_INVOKABLE void importMaterial(); - Q_INVOKABLE void exportMaterial(); + Q_INVOKABLE void exportMaterial(int idx); Q_INVOKABLE void addQtQuick3D(); StudioQuickWidget *quickWidget() const;