QmlDesigner: Unify the selection for nodes, materials, and textures

* Multiple nodes can be selected in MaterialBrowser
* If a node is selected in MaterialBrowser, it's also selected in
the document model.
* If multiple materials/textures are selected from the outside of the
MaterialBrowser, they are marked as selected in Material Browser
* Right-clicking on a material/texture does not select it. The reason
is that the user should be able to apply a texture to a
material/model
* The thick border of the focusMaterialSection is removed
* A dashed-border is added to illustrate the right-clicked item
* Selected items are exposed as roles for both models
(material and texture).
* The item found in the search is exposed as MatchedSearch role
* `selectedMaterialIsComponent` is removed, and instead, a role is
added to the MaterialBrowserModel

Task-number: QDS-14623
Change-Id: Id0a3bd76ae795f276c36483bcc52df487070f8e4
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
Ali Kianian
2025-03-05 14:07:29 +02:00
parent a9a4448ea5
commit b9408c7ed7
17 changed files with 446 additions and 397 deletions

View File

@@ -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
}
}
}

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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()
}
}

View File

@@ -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
}
}

View File

@@ -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 {}

View File

@@ -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
}
}

View File

@@ -234,6 +234,26 @@ QList<ModelNode> getSelectedModels(AbstractView *view)
});
}
QList<ModelNode> getSelectedTextures(AbstractView *view)
{
if (!view || !view->model())
return {};
return Utils::filtered(view->selectedModelNodes(), [](const ModelNode &node) {
return node.metaInfo().isQtQuick3DTexture();
});
}
QList<ModelNode> 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<ModelNode> &models, bool add)
{

View File

@@ -43,6 +43,8 @@ ModelNode selectedTexture(AbstractView *view);
ModelNode resolveSceneEnv(AbstractView *view, int sceneId);
QList<ModelNode> getSelectedModels(AbstractView *view);
QList<ModelNode> getSelectedTextures(AbstractView *view);
QList<ModelNode> getSelectedMaterials(AbstractView *view);
void applyMaterialToModels(AbstractView *view, const ModelNode &material,
const QList<ModelNode> &models, bool add = false);

View File

@@ -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();
default:
return {};
};
}
bool MaterialBrowserModel::isVisible(int idx) const
@@ -138,11 +138,13 @@ void MaterialBrowserModel::unloadPropertyGroups()
QHash<int, QByteArray> MaterialBrowserModel::roleNames() const
{
static const QHash<int, QByteArray> roles{
{Qt::UserRole + 1, "materialName"},
{Qt::UserRole + 2, "materialInternalId"},
{Qt::UserRole + 3, "materialVisible"},
{Qt::UserRole + 4, "materialType"},
{Qt::UserRole + 5, "hasDynamicProperties"}
{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);
for (int i = 0; i < m_materialList.size(); ++i) {
if (isVisible(i)) {
isEmpty = false;
break;
}
++inc;
isEmpty = !isValidIndex(m_selectedIndex + inc)
&& !isValidIndex(m_selectedIndex - inc);
}
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<ModelNode> &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<int> 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<ModelNode> &selectedNodes,
const QList<ModelNode> &deselectedNodes)
{
if (m_materialList.size() == 0) {
m_selectedIndex = -1;
emit selectedIndexChanged(m_selectedIndex);
QList<int> 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<int, int>;
const QList<Bound> &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;
const QModelIndex &mIdx = index(idx);
emit dataChanged(mIdx, mIdx, {Roles::IsComponentRole});
}
idx = std::max(0, std::min(idx, rowCount() - 1));
void MaterialBrowserModel::selectMaterial(int idx, bool appendMat)
{
if (!isValidIndex(idx))
return;
if (idx != m_selectedIndex || force) {
m_selectedIndex = idx;
emit selectedIndexChanged(idx);
ModelNode mat = m_materialList.at(idx);
QTC_ASSERT(mat, return);
m_selectedMaterialIsComponent = selectedMaterial().isComponent();
emit selectedMaterialIsComponentChanged();
}
if (appendMat)
mat.view()->selectModelNode(mat);
else
mat.selectNode();
}
void MaterialBrowserModel::duplicateMaterial(int idx)

View File

@@ -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<ModelNode> materials() const;
void setMaterials(const QList<ModelNode> &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<ModelNode> &selectedNodes,
const QList<ModelNode> &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 &section);
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<QmlDesigner::MaterialBrowserModel::PropertyCopyData> &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<ModelNode> m_materialList;
QStringList m_defaultMaterialSections;
@@ -127,14 +134,12 @@ private:
QHash<qint32, int> 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<MaterialBrowserView> m_view;

View File

@@ -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<int, QByteArray> MaterialBrowserTexturesModel::roleNames() const
{
static const QHash<int, QByteArray> roles{
@@ -110,7 +126,8 @@ QHash<int, QByteArray> 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);
for (int i = 0; i < m_textureList.size(); ++i) {
if (isVisible(i)) {
isEmpty = false;
break;
}
++inc;
isEmpty = !isValidIndex(m_selectedIndex + inc)
&& !isValidIndex(m_selectedIndex - inc);
}
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<ModelNode> &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<int> 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<ModelNode> &selectedNodes,
const QList<ModelNode> &deselectedNodes)
{
if (!m_textureList.isEmpty() && m_selectedIndex < 0) {
ModelNode tex = Utils3D::selectedTexture(m_view);
m_selectedIndex = textureIndex(tex);
}
QList<int> 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<int, int>;
const QList<Bound> &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

View File

@@ -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<ModelNode> textures() const;
void setTextures(const QList<ModelNode> &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<ModelNode> &selectedNodes,
const QList<ModelNode> &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<ModelNode> m_textureList;
QHash<qint32, int> 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<MaterialBrowserView> m_view;
enum {
enum Roles {
RoleTexHasDynamicProps = Qt::UserRole + 1,
RoleTexInternalId,
RoleTexName,
RoleTexSource,
RoleTexToolTip,
RoleTexVisible
RoleMatchedSearch,
RoleTexSelected,
};
};

View File

@@ -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<ModelNode> 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<T> &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<T> &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<ModelNode> &selectedNodeList,
[[maybe_unused]] const QList<ModelNode> &lastSelectedNodeList)
{
const QList<ModelNode> 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 QList<QPair<ModelNode, P
{
for (const auto &nodeProp : propertyList) {
ModelNode node = nodeProp.first;
if (node.metaInfo().isQtQuick3DMaterial())
if (isMaterial(node))
m_previewRequests.insert(node);
}
if (!m_previewRequests.isEmpty() && !m_previewTimer.isActive()) {
@@ -659,10 +553,6 @@ void MaterialBrowserView::auxiliaryDataChanged(const ModelNode &,
{
if (type == Utils3D::active3dSceneProperty)
active3DSceneChanged(data.toInt());
else if (type == Utils3D::matLibSelectedMaterialProperty)
updateMaterialSelection();
else if ( type == Utils3D::matLibSelectedTextureProperty)
updateTextureSelection();
}
void MaterialBrowserView::applyTextureToModel3D(const QmlObjectNode &model3D, const ModelNode &texture)
@@ -762,6 +652,49 @@ void MaterialBrowserView::closeChooseMatPropsView()
m_chooseMatPropsView->close();
}
/*!
* \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<QPair<int, int>> MaterialBrowserView::getSortedBounds(const QList<int> &values)
{
using Bound = QPair<int, int>;
QList<int> sortedValues = Utils::sorted(values);
Bound tempBound;
QList<Bound> 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) {

View File

@@ -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<ModelNode> &nodeList, const QList<QVariant> &data) override;
@@ -68,6 +66,8 @@ public:
Q_INVOKABLE void applyTextureToProperty(const QString &matId, const QString &propName);
Q_INVOKABLE void closeChooseMatPropsView();
static QList<QPair<int, int>> getSortedBounds(const QList<int> &values);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
@@ -79,33 +79,24 @@ private:
template<typename T, typename = typename std::enable_if<std::is_base_of<AbstractProperty, T>::value>::type>
void updatePropertyList(const QList<T> &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<MaterialBrowserWidget> 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<ModelNode> m_previewRequests;
QPointer<QQuickView> m_chooseMatPropsView;
QHash<QString, QList<PropertyName>> 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

View File

@@ -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<QToolButton *> 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<ModelNode>();
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()

View File

@@ -50,7 +50,7 @@ public:
QPointer<MaterialBrowserModel> materialBrowserModel() const;
QPointer<MaterialBrowserTexturesModel> 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<QUrl> &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;