forked from qt-creator/qt-creator
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:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -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 {}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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<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"}
|
||||
static const QHash<int, QByteArray> 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<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;
|
||||
}
|
||||
|
||||
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)
|
||||
|
@@ -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 §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<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;
|
||||
|
@@ -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);
|
||||
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<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
|
||||
|
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -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) {
|
||||
|
@@ -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
|
||||
|
@@ -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()
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user