forked from qt-creator/qt-creator
Drag now correctly accepts only assets that can be used as textures. "selected_texture_changed" custom notification was used in two semantically slightly different cases, both to indicate selected texture had changed and that selection should change, which was little confusing. Split the two cases to separate custom notifications to clarify the situation and allow "select_texture" to be handled in material browser even if it sent it. This fixes the issue of newly added texture not getting selected after drag, because creation was done by material browser view. Similar issue was fixed with "selected_material_changed" as well. Also fixed a couple of cases of drag not being properly ended. Change-Id: Ie1cae01ef13b687d9e611ac1c91443688001fe49 Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
706 lines
27 KiB
C++
706 lines
27 KiB
C++
// Copyright (C) 2022 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
|
|
|
#include "materialbrowserview.h"
|
|
|
|
#include "bindingproperty.h"
|
|
#include "createtexture.h"
|
|
#include "materialbrowsermodel.h"
|
|
#include "materialbrowsertexturesmodel.h"
|
|
#include "materialbrowserwidget.h"
|
|
#include "nodeabstractproperty.h"
|
|
#include "nodemetainfo.h"
|
|
#include "qmlobjectnode.h"
|
|
#include "variantproperty.h"
|
|
|
|
#include <designmodecontext.h>
|
|
#include <nodeinstanceview.h>
|
|
#include <nodelistproperty.h>
|
|
#include <qmldesignerconstants.h>
|
|
|
|
#include <coreplugin/icore.h>
|
|
#include <coreplugin/messagebox.h>
|
|
|
|
#include <projectexplorer/project.h>
|
|
#include <projectexplorer/projecttree.h>
|
|
|
|
#include <utils/algorithm.h>
|
|
#include <utils/qtcassert.h>
|
|
|
|
#include <QQmlContext>
|
|
#include <QQmlEngine>
|
|
#include <QQuickItem>
|
|
#include <QQuickView>
|
|
#include <QRegularExpression>
|
|
#include <QTimer>
|
|
|
|
namespace QmlDesigner {
|
|
|
|
static QString propertyEditorResourcesPath()
|
|
{
|
|
#ifdef SHARE_QML_PATH
|
|
if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
|
|
return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources";
|
|
#endif
|
|
return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString();
|
|
}
|
|
|
|
MaterialBrowserView::MaterialBrowserView(AsynchronousImageCache &imageCache,
|
|
ExternalDependenciesInterface &externalDependencies)
|
|
: AbstractView{externalDependencies}
|
|
, m_imageCache(imageCache)
|
|
{
|
|
m_previewTimer.setSingleShot(true);
|
|
connect(&m_previewTimer, &QTimer::timeout, this, &MaterialBrowserView::requestPreviews);
|
|
}
|
|
|
|
MaterialBrowserView::~MaterialBrowserView()
|
|
{}
|
|
|
|
bool MaterialBrowserView::hasWidget() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
WidgetInfo MaterialBrowserView::widgetInfo()
|
|
{
|
|
if (m_widget.isNull()) {
|
|
m_widget = new MaterialBrowserWidget(m_imageCache, this);
|
|
|
|
auto matEditorContext = new Internal::MaterialBrowserContext(m_widget.data());
|
|
Core::ICore::addContextObject(matEditorContext);
|
|
|
|
// custom notifications below are sent to the MaterialEditor
|
|
MaterialBrowserModel *matBrowserModel = m_widget->materialBrowserModel().data();
|
|
|
|
connect(matBrowserModel, &MaterialBrowserModel::selectedIndexChanged, this, [&] (int idx) {
|
|
ModelNode matNode = m_widget->materialBrowserModel()->materialAt(idx);
|
|
emitCustomNotification("selected_material_changed", {matNode}, {});
|
|
});
|
|
|
|
connect(matBrowserModel, &MaterialBrowserModel::applyToSelectedTriggered, this,
|
|
[&] (const ModelNode &material, bool add) {
|
|
emitCustomNotification("apply_to_selected_triggered", {material}, {add});
|
|
});
|
|
|
|
connect(matBrowserModel, &MaterialBrowserModel::renameMaterialTriggered, this,
|
|
[&] (const ModelNode &material, const QString &newName) {
|
|
emitCustomNotification("rename_material", {material}, {newName});
|
|
});
|
|
|
|
connect(matBrowserModel, &MaterialBrowserModel::addNewMaterialTriggered, this, [&] {
|
|
emitCustomNotification("add_new_material");
|
|
});
|
|
|
|
connect(matBrowserModel, &MaterialBrowserModel::duplicateMaterialTriggered, this,
|
|
[&] (const ModelNode &material) {
|
|
emitCustomNotification("duplicate_material", {material});
|
|
});
|
|
|
|
connect(matBrowserModel, &MaterialBrowserModel::pasteMaterialPropertiesTriggered, this,
|
|
[&] (const ModelNode &material,
|
|
const QList<QmlDesigner::MaterialBrowserModel::PropertyCopyData> &propDatas,
|
|
bool all) {
|
|
QmlObjectNode mat(material);
|
|
executeInTransaction(__FUNCTION__, [&] {
|
|
if (all) { // all material properties copied
|
|
// remove current properties
|
|
PropertyNameList propNames;
|
|
if (mat.isInBaseState()) {
|
|
const QList<AbstractProperty> baseProps = material.properties();
|
|
for (const auto &baseProp : baseProps) {
|
|
if (!baseProp.isDynamic())
|
|
propNames.append(baseProp.name());
|
|
}
|
|
} else {
|
|
QmlPropertyChanges changes = mat.propertyChangeForCurrentState();
|
|
if (changes.isValid()) {
|
|
const QList<AbstractProperty> changedProps = changes.targetProperties();
|
|
for (const auto &changedProp : changedProps) {
|
|
if (!changedProp.isDynamic())
|
|
propNames.append(changedProp.name());
|
|
}
|
|
}
|
|
}
|
|
for (const PropertyName &propName : std::as_const(propNames)) {
|
|
if (propName != "objectName" && propName != "data")
|
|
mat.removeProperty(propName);
|
|
}
|
|
}
|
|
|
|
// apply pasted properties
|
|
for (const QmlDesigner::MaterialBrowserModel::PropertyCopyData &propData : propDatas) {
|
|
if (propData.isValid) {
|
|
const bool isDynamic = !propData.dynamicTypeName.isEmpty();
|
|
const bool isBaseState = currentState().isBaseState();
|
|
const bool hasProperty = mat.hasProperty(propData.name);
|
|
if (propData.isBinding) {
|
|
if (isDynamic && (!hasProperty || isBaseState)) {
|
|
mat.modelNode().bindingProperty(propData.name)
|
|
.setDynamicTypeNameAndExpression(
|
|
propData.dynamicTypeName, propData.value.toString());
|
|
continue;
|
|
}
|
|
mat.setBindingProperty(propData.name, propData.value.toString());
|
|
} else {
|
|
const bool isRecording = mat.timelineIsActive()
|
|
&& mat.currentTimeline().isRecording();
|
|
if (isDynamic && (!hasProperty || (isBaseState && !isRecording))) {
|
|
mat.modelNode().variantProperty(propData.name)
|
|
.setDynamicTypeNameAndValue(
|
|
propData.dynamicTypeName, propData.value);
|
|
continue;
|
|
}
|
|
mat.setVariantProperty(propData.name, propData.value);
|
|
}
|
|
} else {
|
|
mat.removeProperty(propData.name);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// custom notifications below are sent to the TextureEditor
|
|
MaterialBrowserTexturesModel *texturesModel = m_widget->materialBrowserTexturesModel().data();
|
|
connect(texturesModel, &MaterialBrowserTexturesModel::selectedIndexChanged, this, [&] (int idx) {
|
|
ModelNode texNode = m_widget->materialBrowserTexturesModel()->textureAt(idx);
|
|
emitCustomNotification("selected_texture_changed", {texNode});
|
|
});
|
|
connect(texturesModel, &MaterialBrowserTexturesModel::duplicateTextureTriggered, this,
|
|
[&] (const ModelNode &texture) {
|
|
emitCustomNotification("duplicate_texture", {texture});
|
|
});
|
|
|
|
connect(texturesModel, &MaterialBrowserTexturesModel::applyToSelectedMaterialTriggered, this,
|
|
[&] (const ModelNode &texture) {
|
|
if (!m_widget)
|
|
return;
|
|
const ModelNode material = m_widget->materialBrowserModel()->selectedMaterial();
|
|
applyTextureToMaterial({material}, texture);
|
|
});
|
|
|
|
connect(texturesModel, &MaterialBrowserTexturesModel::applyToSelectedModelTriggered, this,
|
|
[&] (const ModelNode &texture) {
|
|
if (m_selectedModels.size() != 1)
|
|
return;
|
|
applyTextureToModel3D(m_selectedModels[0], texture);
|
|
});
|
|
|
|
connect(texturesModel, &MaterialBrowserTexturesModel::addNewTextureTriggered, this, [&] {
|
|
emitCustomNotification("add_new_texture");
|
|
});
|
|
|
|
connect(texturesModel, &MaterialBrowserTexturesModel::updateSceneEnvStateRequested, this, [&]() {
|
|
ModelNode activeSceneEnv = CreateTexture(this).resolveSceneEnv(m_sceneId);
|
|
const bool sceneEnvExists = activeSceneEnv.isValid();
|
|
m_widget->materialBrowserTexturesModel()->setHasSceneEnv(sceneEnvExists);
|
|
});
|
|
|
|
connect(texturesModel, &MaterialBrowserTexturesModel::updateModelSelectionStateRequested, this, [&]() {
|
|
bool hasModel = false;
|
|
if (m_selectedModels.size() == 1)
|
|
hasModel = getMaterialOfModel(m_selectedModels.at(0)).isValid();
|
|
m_widget->materialBrowserTexturesModel()->setHasSingleModelSelection(hasModel);
|
|
});
|
|
|
|
connect(texturesModel, &MaterialBrowserTexturesModel::applyAsLightProbeRequested, this,
|
|
[&] (const ModelNode &texture) {
|
|
executeInTransaction(__FUNCTION__, [&] {
|
|
CreateTexture(this).assignTextureAsLightProbe(texture, m_sceneId);
|
|
});
|
|
});
|
|
}
|
|
|
|
return createWidgetInfo(m_widget.data(),
|
|
"MaterialBrowser",
|
|
WidgetInfo::LeftPane,
|
|
0,
|
|
tr("Material Browser"),
|
|
tr("Material Browser view"));
|
|
}
|
|
|
|
void MaterialBrowserView::createTextures(const QStringList &assetPaths)
|
|
{
|
|
auto *create = new CreateTextures(this, false);
|
|
|
|
executeInTransaction("MaterialBrowserView::createTextures", [&]() {
|
|
create->execute(assetPaths, AddTextureMode::Texture, m_sceneId);
|
|
});
|
|
|
|
create->deleteLater();
|
|
}
|
|
|
|
void MaterialBrowserView::modelAttached(Model *model)
|
|
{
|
|
AbstractView::modelAttached(model);
|
|
|
|
m_widget->clearSearchFilter();
|
|
m_widget->materialBrowserModel()->setHasMaterialLibrary(false);
|
|
m_hasQuick3DImport = model->hasImport("QtQuick3D");
|
|
|
|
// Project load is already very busy and may even trigger puppet reset, so let's wait a moment
|
|
// before refreshing the model
|
|
QTimer::singleShot(1000, model, [this]() {
|
|
refreshModel(true);
|
|
loadPropertyGroups(); // Needs the delay because it uses metaInfo
|
|
});
|
|
|
|
m_sceneId = model->active3DSceneId();
|
|
}
|
|
|
|
void MaterialBrowserView::refreshModel(bool updateImages)
|
|
{
|
|
if (!model())
|
|
return;
|
|
|
|
ModelNode matLib = modelNodeForId(Constants::MATERIAL_LIB_ID);
|
|
QList<ModelNode> materials;
|
|
QList<ModelNode> textures;
|
|
|
|
if (m_hasQuick3DImport && matLib.isValid()) {
|
|
const QList <ModelNode> matLibNodes = matLib.directSubModelNodes();
|
|
for (const ModelNode &node : matLibNodes) {
|
|
if (isMaterial(node))
|
|
materials.append(node);
|
|
else if (isTexture(node))
|
|
textures.append(node);
|
|
}
|
|
}
|
|
|
|
m_widget->materialBrowserModel()->setMaterials(materials, m_hasQuick3DImport);
|
|
m_widget->materialBrowserTexturesModel()->setTextures(textures);
|
|
m_widget->materialBrowserModel()->setHasMaterialLibrary(matLib.isValid());
|
|
|
|
if (updateImages) {
|
|
for (const ModelNode &node : std::as_const(materials))
|
|
m_previewRequests.insert(node);
|
|
if (!m_previewRequests.isEmpty())
|
|
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();
|
|
|
|
if (m_propertyGroupsLoaded) {
|
|
m_propertyGroupsLoaded = false;
|
|
m_widget->materialBrowserModel()->unloadPropertyGroups();
|
|
}
|
|
|
|
AbstractView::modelAboutToBeDetached(model);
|
|
}
|
|
|
|
void MaterialBrowserView::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
|
|
[[maybe_unused]] const QList<ModelNode> &lastSelectedNodeList)
|
|
{
|
|
m_selectedModels = Utils::filtered(selectedNodeList, [](const ModelNode &node) {
|
|
return node.metaInfo().isQtQuick3DModel();
|
|
});
|
|
|
|
m_widget->materialBrowserModel()->setHasModelSelection(!m_selectedModels.isEmpty());
|
|
|
|
// the logic below selects the material of the first selected model if auto selection is on
|
|
if (!m_autoSelectModelMaterial)
|
|
return;
|
|
|
|
if (selectedNodeList.size() > 1 || m_selectedModels.isEmpty())
|
|
return;
|
|
|
|
ModelNode mat = getMaterialOfModel(m_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);
|
|
}
|
|
|
|
void MaterialBrowserView::modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap)
|
|
{
|
|
if (isMaterial(node))
|
|
m_widget->updateMaterialPreview(node, pixmap);
|
|
}
|
|
|
|
void MaterialBrowserView::nodeIdChanged(const ModelNode &node, [[maybe_unused]] const QString &newId,
|
|
[[maybe_unused]] const QString &oldId)
|
|
{
|
|
if (isTexture(node))
|
|
m_widget->materialBrowserTexturesModel()->updateTextureSource(node);
|
|
}
|
|
|
|
void MaterialBrowserView::variantPropertiesChanged(const QList<VariantProperty> &propertyList,
|
|
[[maybe_unused]] PropertyChangeFlags propertyChange)
|
|
{
|
|
for (const VariantProperty &property : propertyList) {
|
|
ModelNode node(property.parentModelNode());
|
|
if (isMaterial(node) && property.name() == "objectName") {
|
|
m_widget->materialBrowserModel()->updateMaterialName(node);
|
|
} else if (property.name() == "source") {
|
|
QmlObjectNode selectedTex = m_widget->materialBrowserTexturesModel()->selectedTexture();
|
|
if (isTexture(node))
|
|
m_widget->materialBrowserTexturesModel()->updateTextureSource(node);
|
|
else if (selectedTex.propertyChangeForCurrentState() == node)
|
|
m_widget->materialBrowserTexturesModel()->updateTextureSource(selectedTex);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserView::propertiesRemoved(const QList<AbstractProperty> &propertyList)
|
|
{
|
|
for (const AbstractProperty &prop : propertyList) {
|
|
if (isTexture(prop.parentModelNode()) && prop.name() == "source")
|
|
m_widget->materialBrowserTexturesModel()->updateTextureSource(prop.parentModelNode());
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserView::nodeReparented(const ModelNode &node,
|
|
const NodeAbstractProperty &newPropertyParent,
|
|
const NodeAbstractProperty &oldPropertyParent,
|
|
[[maybe_unused]] PropertyChangeFlags propertyChange)
|
|
{
|
|
Q_UNUSED(propertyChange)
|
|
|
|
if (node.id() == Constants::MATERIAL_LIB_ID)
|
|
m_widget->materialBrowserModel()->setHasMaterialLibrary(true);
|
|
|
|
if (!isMaterial(node) && !isTexture(node))
|
|
return;
|
|
|
|
ModelNode newParentNode = newPropertyParent.parentModelNode();
|
|
ModelNode oldParentNode = oldPropertyParent.parentModelNode();
|
|
bool added = newParentNode.id() == Constants::MATERIAL_LIB_ID;
|
|
bool removed = oldParentNode.id() == Constants::MATERIAL_LIB_ID;
|
|
|
|
if (!added && !removed)
|
|
return;
|
|
|
|
refreshModel(removed);
|
|
|
|
if (isMaterial(node)) {
|
|
if (added && !m_puppetResetPending) {
|
|
// Workaround to fix various material issues all likely caused by QTBUG-103316
|
|
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();
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserView::nodeAboutToBeRemoved(const ModelNode &removedNode)
|
|
{
|
|
// removing the material lib node
|
|
if (removedNode.id() == Constants::MATERIAL_LIB_ID) {
|
|
m_widget->materialBrowserModel()->setMaterials({}, m_hasQuick3DImport);
|
|
m_widget->materialBrowserModel()->setHasMaterialLibrary(false);
|
|
m_widget->clearPreviewCache();
|
|
return;
|
|
}
|
|
|
|
// not under the material lib
|
|
if (removedNode.parentProperty().parentModelNode().id() != Constants::MATERIAL_LIB_ID)
|
|
return;
|
|
|
|
if (isMaterial(removedNode))
|
|
m_widget->materialBrowserModel()->removeMaterial(removedNode);
|
|
else if (isTexture(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())
|
|
return;
|
|
|
|
QString matPropsPath = model()->metaInfo("QtQuick3D.Material").importDirectoryPath()
|
|
+ "/designer/propertyGroups.json";
|
|
m_propertyGroupsLoaded = m_widget->materialBrowserModel()->loadPropertyGroups(matPropsPath);
|
|
}
|
|
|
|
void MaterialBrowserView::requestPreviews()
|
|
{
|
|
if (model() && model()->nodeInstanceView()) {
|
|
for (const auto &node : std::as_const(m_previewRequests))
|
|
model()->nodeInstanceView()->previewImageDataForGenericNode(node, {});
|
|
}
|
|
m_previewRequests.clear();
|
|
}
|
|
|
|
ModelNode MaterialBrowserView::getMaterialOfModel(const ModelNode &model)
|
|
{
|
|
QmlObjectNode qmlObjNode(model);
|
|
QString matExp = qmlObjNode.expression("materials");
|
|
if (matExp.isEmpty())
|
|
return {};
|
|
|
|
const QStringList mats = matExp.remove('[').remove(']').split(',', Qt::SkipEmptyParts);
|
|
if (mats.isEmpty())
|
|
return {};
|
|
|
|
for (const auto &matId : mats) {
|
|
ModelNode mat = modelNodeForId(matId);
|
|
if (mat.isValid())
|
|
return mat;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void MaterialBrowserView::importsChanged([[maybe_unused]] const QList<Import> &addedImports,
|
|
[[maybe_unused]] const QList<Import> &removedImports)
|
|
{
|
|
bool hasQuick3DImport = model()->hasImport("QtQuick3D");
|
|
|
|
if (hasQuick3DImport == m_hasQuick3DImport)
|
|
return;
|
|
|
|
m_hasQuick3DImport = hasQuick3DImport;
|
|
|
|
loadPropertyGroups();
|
|
|
|
// Import change will trigger puppet reset, so we don't want to update previews immediately
|
|
refreshModel(false);
|
|
}
|
|
|
|
void MaterialBrowserView::customNotification(const AbstractView *view,
|
|
const QString &identifier,
|
|
const QList<ModelNode> &nodeList,
|
|
const QList<QVariant> &data)
|
|
{
|
|
if (view == this && identifier != "select_texture")
|
|
return;
|
|
|
|
if (identifier == "select_material") {
|
|
int idx = m_widget->materialBrowserModel()->materialIndex(nodeList.first());
|
|
if (idx != -1)
|
|
m_widget->materialBrowserModel()->selectMaterial(idx);
|
|
} else if (identifier == "select_texture") {
|
|
int idx = m_widget->materialBrowserTexturesModel()->textureIndex(nodeList.first());
|
|
if (idx != -1) {
|
|
m_widget->materialBrowserTexturesModel()->selectTexture(idx);
|
|
m_widget->materialBrowserTexturesModel()->refreshSearch();
|
|
if (!data.isEmpty() && data[0].toBool())
|
|
m_widget->focusMaterialSection(false);
|
|
}
|
|
} else if (identifier == "refresh_material_browser") {
|
|
QTimer::singleShot(0, model(), [this]() {
|
|
refreshModel(true);
|
|
});
|
|
} else if (identifier == "delete_selected_material") {
|
|
m_widget->deleteSelectedItem();
|
|
} else if (identifier == "apply_asset_to_model3D") {
|
|
m_appliedTexturePath = data.at(0).toString();
|
|
applyTextureToModel3D(nodeList.at(0));
|
|
} else if (identifier == "apply_texture_to_model3D") {
|
|
applyTextureToModel3D(nodeList.at(0), nodeList.at(1));
|
|
} else if (identifier == "apply_texture_to_material") {
|
|
applyTextureToMaterial({nodeList.at(0)}, nodeList.at(1));
|
|
} else if (identifier == "focus_material_section") {
|
|
m_widget->focusMaterialSection(true);
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserView::active3DSceneChanged(qint32 sceneId)
|
|
{
|
|
m_sceneId = sceneId;
|
|
}
|
|
|
|
void MaterialBrowserView::currentStateChanged([[maybe_unused]] const ModelNode &node)
|
|
{
|
|
m_widget->materialBrowserTexturesModel()->updateAllTexturesSources();
|
|
}
|
|
|
|
void MaterialBrowserView::instancesCompleted(const QVector<ModelNode> &completedNodeList)
|
|
{
|
|
for (const ModelNode &node : completedNodeList) {
|
|
// We use root node completion as indication of puppet reset
|
|
if (node.isRootNode()) {
|
|
m_puppetResetPending = false;
|
|
QTimer::singleShot(1000, this, [this]() {
|
|
if (!model() || !model()->nodeInstanceView())
|
|
return;
|
|
const QList<ModelNode> materials = m_widget->materialBrowserModel()->materials();
|
|
for (const ModelNode &node : materials)
|
|
m_previewRequests.insert(node);
|
|
if (!m_previewRequests.isEmpty())
|
|
m_previewTimer.start(0);
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserView::instancePropertyChanged(const QList<QPair<ModelNode, PropertyName> > &propertyList)
|
|
{
|
|
for (const auto &nodeProp : propertyList) {
|
|
ModelNode node = nodeProp.first;
|
|
if (node.metaInfo().isQtQuick3DMaterial())
|
|
m_previewRequests.insert(node);
|
|
}
|
|
if (!m_previewRequests.isEmpty() && !m_previewTimer.isActive()) {
|
|
// Updating material browser isn't urgent in e.g. timeline scrubbing case, so have a bit
|
|
// of delay to reduce unnecessary rendering
|
|
m_previewTimer.start(500);
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserView::applyTextureToModel3D(const QmlObjectNode &model3D, const ModelNode &texture)
|
|
{
|
|
if (!texture.isValid() && m_appliedTexturePath.isEmpty())
|
|
return;
|
|
|
|
if (!model3D.isValid() || !model3D.modelNode().metaInfo().isQtQuick3DModel())
|
|
return;
|
|
|
|
BindingProperty matsProp = model3D.bindingProperty("materials");
|
|
QList<ModelNode> materials;
|
|
if (hasId(matsProp.expression()))
|
|
materials.append(modelNodeForId(matsProp.expression()));
|
|
else
|
|
materials = matsProp.resolveToModelNodeList();
|
|
|
|
applyTextureToMaterial(materials, texture);
|
|
}
|
|
|
|
void MaterialBrowserView::applyTextureToMaterial(const QList<ModelNode> &materials,
|
|
const ModelNode &texture)
|
|
{
|
|
if (materials.isEmpty())
|
|
return;
|
|
|
|
if (texture.isValid())
|
|
m_appliedTextureId = texture.id();
|
|
|
|
m_textureModels.clear();
|
|
QStringList materialsModel;
|
|
for (const ModelNode &mat : std::as_const(materials)) {
|
|
QString matName = mat.variantProperty("objectName").value().toString();
|
|
materialsModel.append(QLatin1String("%1 (%2)").arg(matName, mat.id()));
|
|
QList<PropertyName> texProps;
|
|
for (const PropertyMetaInfo &p : mat.metaInfo().properties()) {
|
|
if (p.propertyType().isQtQuick3DTexture())
|
|
texProps.append(p.name());
|
|
}
|
|
m_textureModels.insert(mat.id(), texProps);
|
|
}
|
|
|
|
QString path = MaterialBrowserWidget::qmlSourcesPath() + "/ChooseMaterialProperty.qml";
|
|
|
|
m_chooseMatPropsView = new QQuickView;
|
|
m_chooseMatPropsView->setTitle(tr("Select a material property"));
|
|
m_chooseMatPropsView->setResizeMode(QQuickView::SizeRootObjectToView);
|
|
m_chooseMatPropsView->setMinimumSize({150, 100});
|
|
m_chooseMatPropsView->setMaximumSize({600, 400});
|
|
m_chooseMatPropsView->setWidth(450);
|
|
m_chooseMatPropsView->setHeight(300);
|
|
m_chooseMatPropsView->setFlags(Qt::Widget);
|
|
m_chooseMatPropsView->setModality(Qt::ApplicationModal);
|
|
m_chooseMatPropsView->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
|
|
m_chooseMatPropsView->rootContext()->setContextProperties({
|
|
{"rootView", QVariant::fromValue(this)},
|
|
{"materialsModel", QVariant::fromValue(materialsModel)},
|
|
{"propertiesModel", QVariant::fromValue(m_textureModels.value(materials.at(0).id()))},
|
|
});
|
|
m_chooseMatPropsView->setSource(QUrl::fromLocalFile(path));
|
|
m_chooseMatPropsView->installEventFilter(this);
|
|
m_chooseMatPropsView->show();
|
|
}
|
|
|
|
void MaterialBrowserView::updatePropsModel(const QString &matId)
|
|
{
|
|
m_chooseMatPropsView->rootContext()->setContextProperty("propertiesModel",
|
|
QVariant::fromValue(m_textureModels.value(matId)));
|
|
}
|
|
|
|
void MaterialBrowserView::applyTextureToProperty(const QString &matId, const QString &propName)
|
|
{
|
|
executeInTransaction(__FUNCTION__, [&] {
|
|
if (m_appliedTextureId.isEmpty() && !m_appliedTexturePath.isEmpty()) {
|
|
bool import = true;
|
|
using namespace ProjectExplorer;
|
|
|
|
if (Project *proj = ProjectTree::currentProject()) {
|
|
Utils::FilePath projDir = proj->projectFilePath().parentDir().pathAppended("content");
|
|
if (m_appliedTexturePath.startsWith(projDir.toString()))
|
|
import = false;
|
|
}
|
|
|
|
auto texCreator = new CreateTexture(this, import);
|
|
ModelNode tex = texCreator->execute(m_appliedTexturePath, AddTextureMode::Texture);
|
|
m_appliedTextureId = tex.id();
|
|
m_appliedTexturePath.clear();
|
|
texCreator->deleteLater();
|
|
}
|
|
|
|
QTC_ASSERT(!m_appliedTextureId.isEmpty(), return);
|
|
|
|
QmlObjectNode mat = modelNodeForId(matId);
|
|
QTC_ASSERT(mat.isValid(), return);
|
|
|
|
BindingProperty texProp = mat.bindingProperty(propName.toLatin1());
|
|
QTC_ASSERT(texProp.isValid(), return);
|
|
|
|
mat.setBindingProperty(propName.toLatin1(), m_appliedTextureId);
|
|
|
|
closeChooseMatPropsView();
|
|
});
|
|
}
|
|
|
|
void MaterialBrowserView::closeChooseMatPropsView()
|
|
{
|
|
m_chooseMatPropsView->close();
|
|
}
|
|
|
|
bool MaterialBrowserView::eventFilter(QObject *obj, QEvent *event)
|
|
{
|
|
if (event->type() == QEvent::KeyPress) {
|
|
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
|
if (keyEvent->key() == Qt::Key_Escape) {
|
|
if (obj == m_chooseMatPropsView)
|
|
m_chooseMatPropsView->close();
|
|
}
|
|
} else if (event->type() == QEvent::Close) {
|
|
if (obj == m_chooseMatPropsView) {
|
|
m_appliedTextureId.clear();
|
|
m_appliedTexturePath.clear();
|
|
m_chooseMatPropsView->deleteLater();
|
|
}
|
|
}
|
|
|
|
return AbstractView::eventFilter(obj, event);
|
|
}
|
|
|
|
} // namespace QmlDesigner
|