QmlDesigner: Implement Material Editor

Task-number: QDS-6438
Task-number: QDS-6439
Change-Id: I04e899a68aea665f0df8b65e21523632174ec76b
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Samuel Ghinet <samuel.ghinet@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Mahmoud Badri
2022-03-18 17:28:28 +02:00
parent 2e8bfee966
commit f09d4538e7
58 changed files with 4329 additions and 250 deletions

View File

@@ -312,6 +312,23 @@ extend_qtc_plugin(QmlDesigner
quick2propertyeditorview.cpp quick2propertyeditorview.h
)
extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/materialeditor
SOURCES
materialeditorcontextobject.cpp materialeditorcontextobject.h
materialeditorqmlbackend.cpp materialeditorqmlbackend.h
materialeditortransaction.cpp materialeditortransaction.h
materialeditorview.cpp materialeditorview.h
)
extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/materialbrowser
SOURCES
materialbrowserview.cpp materialbrowserview.h
materialbrowserwidget.cpp materialbrowserwidget.h
materialbrowsermodel.cpp materialbrowsermodel.h
)
extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components
SOURCES resources/resources.qrc

View File

@@ -80,6 +80,7 @@ const char mergeTemplateCommandId[] = "MergeTemplate";
const char goToImplementationCommandId[] = "GoToImplementation";
const char addSignalHandlerCommandId[] = "AddSignalHandler";
const char moveToComponentCommandId[] = "MoveToComponent";
const char editMaterialCommandId[] = "EditMaterial";
const char addItemToStackedContainerCommandId[] = "AddItemToStackedContainer";
const char addTabBarToStackedContainerCommandId[] = "AddTabBarToStackedContainer";
const char increaseIndexOfStackedContainerCommandId[] = "IncreaseIndexOfStackedContainer";
@@ -134,6 +135,7 @@ const char mergeTemplateDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMen
const char goToImplementationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Go to Implementation");
const char addSignalHandlerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Add New Signal Handler");
const char moveToComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Move Component into Separate File");
const char editMaterialDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Material");
const char editAnnotationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Annotation");
const char openSignalDialogDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Signal Dialog");

View File

@@ -1506,6 +1506,17 @@ void DesignerActionManager::createDefaultDesignerActions()
&singleSelection,
&singleSelection));
addDesignerAction(new ModelNodeContextMenuAction(
editMaterialCommandId,
editMaterialDisplayName,
{},
rootCategory,
QKeySequence(),
44,
&editMaterial,
&modelHasMaterial,
&isModel));
addDesignerAction(new ModelNodeContextMenuAction(mergeTemplateCommandId,
mergeTemplateDisplayName,
{},

View File

@@ -27,6 +27,7 @@
#include "modelnodeoperations.h"
#include "abstractaction.h"
#include "bindingproperty.h"
#include "abstractactiongroup.h"
#include "qmlitemnode.h"
#include <qmldesignerplugin.h>
@@ -63,6 +64,24 @@ inline bool singleSelection(const SelectionContext &selectionState)
return selectionState.singleNodeIsSelected();
}
inline bool isModel(const SelectionContext &selectionState)
{
ModelNode node = selectionState.currentSingleSelectedNode();
return node.isValid() && node.isSubclassOf("QtQuick3D.Model");
}
inline bool modelHasMaterial(const SelectionContext &selectionState)
{
ModelNode node = selectionState.currentSingleSelectedNode();
if (!node.isValid())
return false;
BindingProperty prop = node.bindingProperty("materials");
return prop.exists() && (!prop.expression().isEmpty() || !prop.resolveToModelNodeList().empty());
}
inline bool selectionEnabled(const SelectionContext &selectionState)
{
return selectionState.showSelectionTools();

View File

@@ -24,6 +24,7 @@
****************************************************************************/
#include "modelnodeoperations.h"
#include "designmodewidget.h"
#include "modelnodecontextmenu_helper.h"
#include "addimagesdialog.h"
#include "layoutingridlayout.h"
@@ -789,6 +790,37 @@ void addNewSignalHandler(const SelectionContext &selectionState)
addSignalHandlerOrGotoImplementation(selectionState, true);
}
// Open a model's material in the material editor
void editMaterial(const SelectionContext &selectionContext)
{
ModelNode modelNode = selectionContext.currentSingleSelectedNode();
QTC_ASSERT(modelNode.isValid(), return);
BindingProperty prop = modelNode.bindingProperty("materials");
if (!prop.exists())
return;
AbstractView *view = selectionContext.view();
ModelNode material;
if (view->hasId(prop.expression())) {
material = view->modelNodeForId(prop.expression());
} else {
QList<ModelNode> materials = prop.resolveToModelNodeList();
if (materials.size() > 0)
material = materials.first();
}
if (material.isValid()) {
QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialEditor");
// to MaterialEditor and MaterialBrowser...
view->emitCustomNotification("selected_material_changed", {material});
}
}
void addItemToStackedContainer(const SelectionContext &selectionContext)
{
AbstractView *view = selectionContext.view();

View File

@@ -67,6 +67,7 @@ void layoutColumnLayout(const SelectionContext &selectionState);
void layoutGridLayout(const SelectionContext &selectionState);
void goImplementation(const SelectionContext &selectionState);
void addNewSignalHandler(const SelectionContext &selectionState);
void editMaterial(const SelectionContext &selectionContext);
void addSignalHandlerOrGotoImplementation(const SelectionContext &selectionState, bool addAlwaysNewSlot);
void removeLayout(const SelectionContext &selectionContext);
void removePositioner(const SelectionContext &selectionContext);

View File

@@ -72,6 +72,7 @@ public:
animatedProperty,
annotationBubble,
annotationDecal,
applyMaterialToSelected,
assign,
bevelAll,
bevelCorner,
@@ -86,8 +87,10 @@ public:
cornersAll,
curveDesigner,
curveEditor,
customMaterialEditor,
decisionNode,
deleteColumn,
deleteMaterial,
deleteRow,
deleteTable,
detach,
@@ -130,6 +133,8 @@ public:
mergeCells,
minus,
mirror,
newMaterial,
openMaterialBrowser,
orientation,
paddingEdge,
paddingFrame,

View File

@@ -30,6 +30,11 @@
#include "nodehints.h"
#include "qmlvisualnode.h"
#include <bindingproperty.h>
#include <nodemetainfo.h>
#include <nodelistproperty.h>
#include <variantproperty.h>
#include <utils/qtcassert.h>
#include <coreplugin/icore.h>
@@ -181,13 +186,41 @@ void Edit3DCanvas::dragEnterEvent(QDragEnterEvent *e)
void Edit3DCanvas::dropEvent(QDropEvent *e)
{
Q_UNUSED(e)
auto modelNode = QmlVisualNode::createQml3DNode(m_parent->view(), m_itemLibraryEntry, m_activeScene).modelNode();
QTC_ASSERT(modelNode.isValid(), return);
if (modelNode.isValid()) {
e->accept();
m_parent->view()->setSelectedModelNode(modelNode);
e->accept();
m_parent->view()->setSelectedModelNode(modelNode);
// if added node is a Model, assign it a material
if (modelNode.isSubclassOf("QtQuick3D.Model")) {
ModelNode matLib = m_parent->view()->modelNodeForId(Constants::MATERIAL_LIB_ID);
QTC_ASSERT(matLib.isValid(), return);
const QList<ModelNode> materials = matLib.directSubModelNodes();
ModelNode material;
if (materials.size() > 0) {
for (const ModelNode &mat : materials) {
if (mat.isSubclassOf("QtQuick3D.Material")) {
material = mat;
break;
}
}
}
// if no valid material, create a new default material
if (!material.isValid()) {
NodeMetaInfo metaInfo = m_parent->view()->model()->metaInfo("QtQuick3D.DefaultMaterial");
material = m_parent->view()->createModelNode("QtQuick3D.DefaultMaterial", metaInfo.majorVersion(),
metaInfo.minorVersion());
VariantProperty matNameProp = material.variantProperty("objectName");
matNameProp.setValue("New Material");
material.validId();
matLib.defaultNodeListProperty().reparentHere(material);
}
BindingProperty modelMatsProp = modelNode.bindingProperty("materials");
modelMatsProp.setExpression(material.id());
}
}

View File

@@ -67,16 +67,7 @@ void Edit3DView::createEdit3DWidget()
void Edit3DView::checkImports()
{
bool has3dImport = false;
const QList<Import> imports = model()->imports();
for (const auto &import : imports) {
if (import.url() == "QtQuick3D") {
has3dImport = true;
break;
}
}
edit3DWidget()->showCanvas(has3dImport);
edit3DWidget()->showCanvas(model()->hasImport("QtQuick3D"));
}
WidgetInfo Edit3DView::widgetInfo()

View File

@@ -0,0 +1,271 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "materialbrowsermodel.h"
#include "variantproperty.h"
#include <designmodewidget.h>
#include <qmldesignerplugin.h>
namespace QmlDesigner {
MaterialBrowserModel::MaterialBrowserModel(QObject *parent)
: QAbstractListModel(parent)
{
}
MaterialBrowserModel::~MaterialBrowserModel()
{
}
int MaterialBrowserModel::rowCount(const QModelIndex &) const
{
return m_materialList.count();
}
QVariant MaterialBrowserModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= m_materialList.count()) {
qWarning() << Q_FUNC_INFO << "invalid index requested";
return {};
}
if (roleNames().value(role) == "materialName") {
QVariant objName = m_materialList.at(index.row()).variantProperty("objectName").value();
return objName.isValid() ? objName : "";
}
if (roleNames().value(role) == "materialInternalId")
return m_materialList.at(index.row()).internalId();
if (roleNames().value(role) == "materialVisible")
return isMaterialVisible(index.row());
if (!roleNames().contains(role))
qWarning() << Q_FUNC_INFO << "invalid role requested";
return {};
}
bool MaterialBrowserModel::isMaterialVisible(int idx) const
{
if (!isValidIndex(idx))
return false;
return m_searchText.isEmpty() || m_materialList.at(idx).variantProperty("objectName")
.value().toString().contains(m_searchText, Qt::CaseInsensitive);
}
bool MaterialBrowserModel::isValidIndex(int idx) const
{
return idx > -1 && idx < rowCount();
}
QHash<int, QByteArray> MaterialBrowserModel::roleNames() const
{
static const QHash<int, QByteArray> roles {
{Qt::UserRole + 1, "materialName"},
{Qt::UserRole + 2, "materialInternalId"},
{Qt::UserRole + 3, "materialVisible"},
};
return roles;
}
bool MaterialBrowserModel::hasQuick3DImport() const
{
return m_hasQuick3DImport;
}
void MaterialBrowserModel::setHasQuick3DImport(bool b)
{
if (b == m_hasQuick3DImport)
return;
m_hasQuick3DImport = b;
emit hasQuick3DImportChanged();
}
void MaterialBrowserModel::setSearchText(const QString &searchText)
{
QString lowerSearchText = searchText.toLower();
if (m_searchText == lowerSearchText)
return;
m_searchText = lowerSearchText;
bool isEmpty = false;
// if selected material goes invisible, select nearest material
if (!isMaterialVisible(m_selectedIndex)) {
int inc = 1;
int incCap = m_materialList.count();
while (!isEmpty && inc < incCap) {
if (isMaterialVisible(m_selectedIndex - inc)) {
selectMaterial(m_selectedIndex - inc);
break;
} else if (isMaterialVisible(m_selectedIndex + inc)) {
selectMaterial(m_selectedIndex + inc);
break;
}
++inc;
isEmpty = !isValidIndex(m_selectedIndex + inc)
&& !isValidIndex(m_selectedIndex - inc);
}
}
if (isEmpty != m_isEmpty) {
m_isEmpty = isEmpty;
emit isEmptyChanged();
}
resetModel();
}
void MaterialBrowserModel::setMaterials(const QList<ModelNode> &materials, bool hasQuick3DImport)
{
m_materialList = materials;
m_materialIndexHash.clear();
for (int i = 0; i < materials.size(); ++i)
m_materialIndexHash.insert(materials.at(i).internalId(), i);
bool isEmpty = materials.size() == 0;
if (isEmpty != m_isEmpty) {
m_isEmpty = isEmpty;
emit isEmptyChanged();
}
if (hasQuick3DImport != m_hasQuick3DImport) {
m_hasQuick3DImport = hasQuick3DImport;
emit hasQuick3DImportChanged();
}
updateSelectedMaterial();
resetModel();
}
void MaterialBrowserModel::removeMaterial(const ModelNode &material)
{
if (!m_materialIndexHash.contains(material.internalId()))
return;
m_materialList.removeOne(material);
int idx = m_materialIndexHash.value(material.internalId());
m_materialIndexHash.remove(material.internalId());
// update index hash
for (int i = idx; i < rowCount(); ++i)
m_materialIndexHash.insert(m_materialList.at(i).internalId(), i);
resetModel();
if (m_materialList.isEmpty()) {
m_isEmpty = true;
emit isEmptyChanged();
}
}
void MaterialBrowserModel::updateSelectedMaterial()
{
selectMaterial(m_selectedIndex, true);
}
void MaterialBrowserModel::updateMaterialName(const ModelNode &material)
{
int idx = materialIndex(material);
if (idx != -1)
emit dataChanged(index(idx, 0), index(idx, 0), {roleNames().key("materialName")});
}
int MaterialBrowserModel::materialIndex(const ModelNode &material) const
{
if (m_materialIndexHash.contains(material.internalId()))
return m_materialIndexHash.value(material.internalId());
return -1;
}
ModelNode MaterialBrowserModel::materialAt(int idx) const
{
if (isValidIndex(idx))
return m_materialList.at(idx);
return {};
}
void MaterialBrowserModel::resetModel()
{
beginResetModel();
endResetModel();
}
void MaterialBrowserModel::selectMaterial(int idx, bool force)
{
if (m_materialList.size() == 0) {
m_selectedIndex = -1;
emit selectedIndexChanged(m_selectedIndex);
return;
}
idx = std::max(0, std::min(idx, rowCount() - 1));
if (idx != m_selectedIndex || force) {
m_selectedIndex = idx;
emit selectedIndexChanged(idx);
}
}
void MaterialBrowserModel::deleteMaterial(qint32 internalId)
{
int idx = m_materialIndexHash.value(internalId);
if (isValidIndex(idx))
m_materialList[idx].destroy();
}
void MaterialBrowserModel::renameMaterial(int idx, const QString &newName)
{
ModelNode mat = m_materialList.at(idx);
emit renameMaterialTriggered(mat, newName);
}
void MaterialBrowserModel::addNewMaterial()
{
emit addNewMaterialTriggered();
}
void MaterialBrowserModel::applyToSelected(qint64 internalId, bool add)
{
int idx = m_materialIndexHash.value(internalId);
if (idx != -1) {
ModelNode mat = m_materialList.at(idx);
emit applyToSelectedTriggered(mat, add);
}
}
void MaterialBrowserModel::openMaterialEditor()
{
QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialEditor");
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,94 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <modelnode.h>
#include <QAbstractListModel>
#include <QObject>
#include <QPointer>
namespace QmlDesigner {
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 hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged)
public:
MaterialBrowserModel(QObject *parent = nullptr);
~MaterialBrowserModel() override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
void setSearchText(const QString &searchText);
bool hasQuick3DImport() const;
void setHasQuick3DImport(bool b);
void setMaterials(const QList<ModelNode> &materials, bool hasQuick3DImport);
void removeMaterial(const ModelNode &material);
void updateMaterialName(const ModelNode &material);
void updateSelectedMaterial();
int materialIndex(const ModelNode &material) const;
ModelNode materialAt(int idx) const;
void resetModel();
Q_INVOKABLE void selectMaterial(int idx, bool force = false);
Q_INVOKABLE void deleteMaterial(int idx);
Q_INVOKABLE void renameMaterial(int idx, const QString &newName);
Q_INVOKABLE void addNewMaterial();
Q_INVOKABLE void applyToSelected(qint64 internalId, bool add = false);
Q_INVOKABLE void openMaterialEditor();
signals:
void isEmptyChanged();
void hasQuick3DImportChanged();
void selectedIndexChanged(int idx);
void renameMaterialTriggered(const QmlDesigner::ModelNode &material, const QString &newName);
void applyToSelectedTriggered(const QmlDesigner::ModelNode &material, bool add = false);
void addNewMaterialTriggered();
private:
bool isMaterialVisible(int idx) const;
bool isValidIndex(int idx) const;
QString m_searchText;
QList<ModelNode> m_materialList;
QHash<qint32, int> m_materialIndexHash; // internalId -> index
int m_selectedIndex = 0;
bool m_isEmpty = true;
bool m_hasQuick3DImport = false;
};
} // namespace QmlDesigner

View File

@@ -0,0 +1,266 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "materialbrowserview.h"
#include "materialbrowserwidget.h"
#include "materialbrowsermodel.h"
#include "nodeabstractproperty.h"
#include "qmlobjectnode.h"
#include "variantproperty.h"
#include <coreplugin/icore.h>
#include <nodeinstanceview.h>
#include <qmldesignerconstants.h>
#include <QQuickItem>
namespace QmlDesigner {
MaterialBrowserView::MaterialBrowserView(QObject *parent)
: AbstractView(parent)
{}
MaterialBrowserView::~MaterialBrowserView()
{}
bool MaterialBrowserView::hasWidget() const
{
return true;
}
WidgetInfo MaterialBrowserView::widgetInfo()
{
if (m_widget.isNull()) {
m_widget = new MaterialBrowserWidget;
connect(m_widget->materialBrowserModel().data(), SIGNAL(selectedIndexChanged(int)),
this, SLOT(handleSelectedMaterialChanged(int)));
connect(m_widget->materialBrowserModel().data(),
SIGNAL(applyToSelectedTriggered(const QmlDesigner::ModelNode &, bool)),
this, SLOT(handleApplyToSelectedTriggered(const QmlDesigner::ModelNode &, bool)));
connect(m_widget->materialBrowserModel().data(),
SIGNAL(renameMaterialTriggered(const QmlDesigner::ModelNode &, const QString &)),
this, SLOT(handleRenameMaterial(const QmlDesigner::ModelNode &, const QString &)));
connect(m_widget->materialBrowserModel().data(), SIGNAL(addNewMaterialTriggered()),
this, SLOT(handleAddNewMaterial()));
}
return createWidgetInfo(m_widget.data(),
new WidgetInfo::ToolBarWidgetDefaultFactory<MaterialBrowserWidget>(m_widget.data()),
"MaterialBrowser",
WidgetInfo::LeftPane,
0,
tr("Material Browser"));
}
void MaterialBrowserView::modelAttached(Model *model)
{
AbstractView::modelAttached(model);
m_widget->clearSearchFilter();
m_hasQuick3DImport = model->hasImport("QtQuick3D");
QTimer::singleShot(0, this, &MaterialBrowserView::refreshModel);
}
void MaterialBrowserView::refreshModel()
{
ModelNode matLib = modelNodeForId(Constants::MATERIAL_LIB_ID);
QList <ModelNode> materials;
if (m_hasQuick3DImport && matLib.isValid()) {
const QList <ModelNode> matLibNodes = matLib.directSubModelNodes();
for (const ModelNode &node : matLibNodes) {
if (isMaterial(node))
materials.append(node);
}
}
m_widget->materialBrowserModel()->setMaterials(materials, m_hasQuick3DImport);
for (const ModelNode &node : std::as_const(materials))
model()->nodeInstanceView()->previewImageDataForGenericNode(node, {});
}
bool MaterialBrowserView::isMaterial(const ModelNode &node) const
{
if (!node.isValid() || node.isComponent())
return false;
return node.isSubclassOf("QtQuick3D.Material");
}
void MaterialBrowserView::modelAboutToBeDetached(Model *model)
{
AbstractView::modelAboutToBeDetached(model);
}
void MaterialBrowserView::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
const QList<ModelNode> &lastSelectedNodeList)
{
if (!m_autoSelectModelMaterial)
return;
// if selected object is a model, select its material in the material browser and editor
ModelNode selectedModel;
for (const ModelNode &node : selectedNodeList) {
if (node.isSubclassOf("QtQuick3D.Model")) {
selectedModel = node;
break;
}
}
if (selectedNodeList.size() > 1 || !selectedModel.isValid())
return;
QmlObjectNode qmlObjNode(selectedModel);
QString matExp = qmlObjNode.expression("materials");
if (matExp.isEmpty())
return;
QString matId = matExp.remove('[').remove(']').split(',', Qt::SkipEmptyParts).at(0);
ModelNode mat = modelNodeForId(matId);
if (!mat.isValid())
return;
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::variantPropertiesChanged(const QList<VariantProperty> &propertyList,
PropertyChangeFlags propertyChange)
{
for (const VariantProperty &property : propertyList) {
ModelNode node(property.parentModelNode());
if (isMaterial(node) && property.name() == "objectName")
m_widget->materialBrowserModel()->updateMaterialName(node);
}
}
void MaterialBrowserView::nodeReparented(const ModelNode &node,
const NodeAbstractProperty &newPropertyParent,
const NodeAbstractProperty &oldPropertyParent,
PropertyChangeFlags propertyChange)
{
if (!isMaterial(node))
return;
ModelNode newParentNode = newPropertyParent.parentModelNode();
ModelNode oldParentNode = oldPropertyParent.parentModelNode();
bool matAdded = newParentNode.isValid() && newParentNode.id() == Constants::MATERIAL_LIB_ID;
bool matRemoved = oldParentNode.isValid() && oldParentNode.id() == Constants::MATERIAL_LIB_ID;
if (matAdded || matRemoved) {
refreshModel();
int idx = m_widget->materialBrowserModel()->materialIndex(node);
m_widget->materialBrowserModel()->selectMaterial(idx);
}
}
void MaterialBrowserView::nodeAboutToBeRemoved(const ModelNode &removedNode)
{
// removing the material editor node
if (removedNode.isValid() && removedNode.id() == Constants::MATERIAL_LIB_ID) {
m_widget->materialBrowserModel()->setMaterials({}, m_hasQuick3DImport);
return;
}
// not a material under the material editor
if (!isMaterial(removedNode)
|| removedNode.parentProperty().parentModelNode().id() != Constants::MATERIAL_LIB_ID) {
return;
}
m_widget->materialBrowserModel()->removeMaterial(removedNode);
}
void MaterialBrowserView::nodeRemoved(const ModelNode &removedNode,
const NodeAbstractProperty &parentProperty,
PropertyChangeFlags propertyChange)
{
if (parentProperty.parentModelNode().id() != Constants::MATERIAL_LIB_ID)
return;
m_widget->materialBrowserModel()->updateSelectedMaterial();
}
void MaterialBrowserView::importsChanged(const QList<Import> &addedImports, const QList<Import> &removedImports)
{
bool hasQuick3DImport = model()->hasImport("QtQuick3D");
if (hasQuick3DImport == m_hasQuick3DImport)
return;
m_hasQuick3DImport = hasQuick3DImport;
refreshModel();
}
void MaterialBrowserView::customNotification(const AbstractView *view, const QString &identifier,
const QList<ModelNode> &nodeList, const QList<QVariant> &data)
{
if (view == this)
return;
if (identifier == "selected_material_changed") {
int idx = m_widget->materialBrowserModel()->materialIndex(nodeList.first());
if (idx != -1)
m_widget->materialBrowserModel()->selectMaterial(idx);
}
}
void MaterialBrowserView::handleSelectedMaterialChanged(int idx)
{
ModelNode matNode = m_widget->materialBrowserModel()->materialAt(idx);
// to MaterialEditor...
emitCustomNotification("selected_material_changed", {matNode}, {});
}
void MaterialBrowserView::handleApplyToSelectedTriggered(const ModelNode &material, bool add)
{
// to MaterialEditor...
emitCustomNotification("apply_to_selected_triggered", {material}, {add});
}
void MaterialBrowserView::handleRenameMaterial(const ModelNode &material, const QString &newName)
{
// to MaterialEditor...
emitCustomNotification("rename_material", {material}, {newName});
}
void MaterialBrowserView::handleAddNewMaterial()
{
// to MaterialEditor...
emitCustomNotification("add_new_material");
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,79 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <abstractview.h>
#include <QPointer>
namespace QmlDesigner {
class MaterialBrowserWidget;
class MaterialBrowserView : public AbstractView
{
Q_OBJECT
public:
MaterialBrowserView(QObject *parent = nullptr);
~MaterialBrowserView() override;
bool hasWidget() const override;
WidgetInfo widgetInfo() override;
// AbstractView
void modelAttached(Model *model) override;
void modelAboutToBeDetached(Model *model) override;
void selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
const QList<ModelNode> &lastSelectedNodeList) override;
void modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap) override;
void variantPropertiesChanged(const QList<VariantProperty> &propertyList, PropertyChangeFlags propertyChange) override;
void nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent,
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 QList<Import> &addedImports, const QList<Import> &removedImports) override;
void customNotification(const AbstractView *view, const QString &identifier,
const QList<ModelNode> &nodeList, const QList<QVariant> &data) override;
private:
void refreshModel();
bool isMaterial(const ModelNode &node) const;
QPointer<MaterialBrowserWidget> m_widget;
bool m_hasQuick3DImport = false;
bool m_autoSelectModelMaterial = false; // TODO: wire this to some action
private slots:
void handleSelectedMaterialChanged(int idx);
void handleApplyToSelectedTriggered(const QmlDesigner::ModelNode &material, bool add = false);
void handleRenameMaterial(const QmlDesigner::ModelNode &material, const QString &newName);
void handleAddNewMaterial();
};
} // namespace QmlDesigner

View File

@@ -0,0 +1,205 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "materialbrowserwidget.h"
#include "materialbrowsermodel.h"
#include <theme.h>
#include <designeractionmanager.h>
#include <designermcumanager.h>
#include <documentmanager.h>
#include <qmldesignerconstants.h>
#include <utils/algorithm.h>
#include <utils/stylehelper.h>
#include <utils/qtcassert.h>
#include <QImageReader>
#include <QMenu>
#include <QMimeData>
#include <QMouseEvent>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickImageProvider>
#include <QQuickItem>
#include <QShortcut>
#include <QStackedWidget>
#include <QTabBar>
#include <QTimer>
#include <QToolButton>
#include <QVBoxLayout>
#include <QWheelEvent>
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();
}
class PreviewImageProvider : public QQuickImageProvider
{
QHash<qint32, QPixmap> m_pixmaps;
public:
PreviewImageProvider()
: QQuickImageProvider(Pixmap) {}
void setPixmap(const ModelNode &node, const QPixmap &pixmap)
{
m_pixmaps.insert(node.internalId(), pixmap);
}
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override
{
Q_UNUSED(requestedSize)
QPixmap pixmap{150, 150};
qint32 internalId = id.toInt();
if (m_pixmaps.contains(internalId))
pixmap = m_pixmaps.value(internalId);
if (size)
*size = pixmap.size();
return pixmap;
}
};
bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::FocusOut) {
if (obj == m_quickWidget.data())
QMetaObject::invokeMethod(m_quickWidget->rootObject(), "closeContextMenu");
}
return QObject::eventFilter(obj, event);
}
MaterialBrowserWidget::MaterialBrowserWidget()
: m_materialBrowserModel(new MaterialBrowserModel(this))
, m_quickWidget(new QQuickWidget(this))
, m_previewImageProvider(new PreviewImageProvider())
{
setWindowTitle(tr("Material Browser", "Title of material browser widget"));
setMinimumWidth(120);
m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
m_quickWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
m_quickWidget->setClearColor(Theme::getColor(Theme::Color::DSpanelBackground));
m_quickWidget->rootContext()->setContextProperties({
{"rootView", QVariant::fromValue(this)},
{"materialBrowserModel", QVariant::fromValue(m_materialBrowserModel.data())},
});
m_quickWidget->engine()->addImageProvider("materialBrowser", m_previewImageProvider);
Theme::setupTheme(m_quickWidget->engine());
m_quickWidget->installEventFilter(this);
auto layout = new QVBoxLayout(this);
layout->setContentsMargins({});
layout->setSpacing(0);
layout->addWidget(m_quickWidget.data());
updateSearch();
setStyleSheet(Theme::replaceCssColors(
QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"))));
m_qmlSourceUpdateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F8), this);
connect(m_qmlSourceUpdateShortcut, &QShortcut::activated, this, &MaterialBrowserWidget::reloadQmlSource);
reloadQmlSource();
}
void MaterialBrowserWidget::updateMaterialPreview(const ModelNode &node, const QPixmap &pixmap)
{
m_previewImageProvider->setPixmap(node, pixmap);
int idx = m_materialBrowserModel->materialIndex(node);
if (idx != -1)
QMetaObject::invokeMethod(m_quickWidget->rootObject(), "refreshPreview", Q_ARG(QVariant, idx));
}
QList<QToolButton *> MaterialBrowserWidget::createToolBarWidgets()
{
return {};
}
void MaterialBrowserWidget::handleSearchfilterChanged(const QString &filterText)
{
if (filterText != m_filterText) {
m_filterText = filterText;
updateSearch();
}
}
QString MaterialBrowserWidget::qmlSourcesPath()
{
#ifdef SHARE_QML_PATH
if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
return QLatin1String(SHARE_QML_PATH) + "/materialBrowserQmlSource";
#endif
return Core::ICore::resourcePath("qmldesigner/materialBrowserQmlSource").toString();
}
void MaterialBrowserWidget::clearSearchFilter()
{
QMetaObject::invokeMethod(m_quickWidget->rootObject(), "clearSearchFilter");
}
void MaterialBrowserWidget::reloadQmlSource()
{
const QString materialBrowserQmlPath = qmlSourcesPath() + "/MaterialBrowser.qml";
QTC_ASSERT(QFileInfo::exists(materialBrowserQmlPath), return);
m_quickWidget->engine()->clearComponentCache();
m_quickWidget->setSource(QUrl::fromLocalFile(materialBrowserQmlPath));
}
void MaterialBrowserWidget::updateSearch()
{
m_materialBrowserModel->setSearchText(m_filterText);
m_quickWidget->update();
}
QQuickWidget *MaterialBrowserWidget::quickWidget() const
{
return m_quickWidget.data();
}
QPointer<MaterialBrowserModel> MaterialBrowserWidget::materialBrowserModel() const
{
return m_materialBrowserModel;
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,91 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "itemlibraryinfo.h"
#include "import.h"
#include "modelnode.h"
#include <utils/fancylineedit.h>
#include <utils/dropsupport.h>
#include <QFrame>
#include <QToolButton>
#include <QFileIconProvider>
#include <QQuickWidget>
#include <QQmlPropertyMap>
#include <QTimer>
#include <QPointF>
#include <memory>
QT_BEGIN_NAMESPACE
class QStackedWidget;
class QShortcut;
QT_END_NAMESPACE
namespace QmlDesigner {
class MaterialBrowserModel;
class PreviewImageProvider;
class MaterialBrowserWidget : public QFrame
{
Q_OBJECT
public:
MaterialBrowserWidget();
~MaterialBrowserWidget() = default;
QList<QToolButton *> createToolBarWidgets();
static QString qmlSourcesPath();
void clearSearchFilter();
QPointer<MaterialBrowserModel> materialBrowserModel() const;
void updateMaterialPreview(const ModelNode &node, const QPixmap &pixmap);
Q_INVOKABLE void handleSearchfilterChanged(const QString &filterText);
QQuickWidget *quickWidget() const;
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
private:
void reloadQmlSource();
void updateSearch();
QPointer<MaterialBrowserModel> m_materialBrowserModel;
QScopedPointer<QQuickWidget> m_quickWidget;
QShortcut *m_qmlSourceUpdateShortcut = nullptr;
PreviewImageProvider *m_previewImageProvider = nullptr;
QString m_filterText;
};
} // namespace QmlDesigner

View File

@@ -0,0 +1,378 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "materialeditorcontextobject.h"
#include <abstractview.h>
#include <nodemetainfo.h>
#include <rewritingexception.h>
#include <qmldesignerplugin.h>
#include <qmlmodelnodeproxy.h>
#include <qmlobjectnode.h>
#include <qmltimeline.h>
#include <coreplugin/messagebox.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <QApplication>
#include <QCursor>
#include <QMessageBox>
#include <QQmlContext>
#include <QWindow>
#include <coreplugin/icore.h>
namespace QmlDesigner {
MaterialEditorContextObject::MaterialEditorContextObject(QObject *parent)
: QObject(parent)
{
qmlRegisterUncreatableType<MaterialEditorContextObject>("ToolBarAction", 1, 0, "ToolBarAction", "Enum type");
}
QString MaterialEditorContextObject::convertColorToString(const QVariant &color)
{
QString colorString;
QColor theColor;
if (color.canConvert(QVariant::Color)) {
theColor = color.value<QColor>();
} else if (color.canConvert(QVariant::Vector3D)) {
auto vec = color.value<QVector3D>();
theColor = QColor::fromRgbF(vec.x(), vec.y(), vec.z());
}
colorString = theColor.name();
if (theColor.alpha() != 255) {
QString hexAlpha = QString("%1").arg(theColor.alpha(), 2, 16, QLatin1Char('0'));
colorString.remove(0, 1);
colorString.prepend(hexAlpha);
colorString.prepend(QStringLiteral("#"));
}
return colorString;
}
// TODO: this method is used by the ColorEditor helper widget, check if at all needed?
QColor MaterialEditorContextObject::colorFromString(const QString &colorString)
{
return colorString;
}
void MaterialEditorContextObject::changeTypeName(const QString &typeName)
{
QTC_ASSERT(m_model && m_model->rewriterView(), return);
QTC_ASSERT(m_selectedMaterial.isValid(), return);
if (m_selectedMaterial.simplifiedTypeName() == typeName)
return;
// Ideally we should not misuse the rewriterView
// If we add more code here we have to forward the material editor view
RewriterView *rewriterView = m_model->rewriterView();
rewriterView->executeInTransaction("MaterialEditorContextObject::changeTypeName", [&] {
NodeMetaInfo metaInfo = m_model->metaInfo(typeName.toLatin1());
QTC_ASSERT(metaInfo.isValid(), return);
// Create a list of properties available for the new type
PropertyNameList propertiesAndSignals(metaInfo.propertyNames());
// Add signals to the list
const PropertyNameList signalNames = metaInfo.signalNames();
for (const PropertyName &signal : signalNames) {
if (signal.isEmpty())
continue;
PropertyName name = signal;
QChar firstChar = QChar(signal.at(0)).toUpper().toLatin1();
name[0] = firstChar.toLatin1();
name.prepend("on");
propertiesAndSignals.append(name);
}
// Add dynamic properties and respective change signals
const QList<AbstractProperty> matProps = m_selectedMaterial.properties();
for (const auto &property : matProps) {
if (!property.isDynamic())
continue;
// Add dynamic property
propertiesAndSignals.append(property.name());
// Add its change signal
PropertyName name = property.name();
QChar firstChar = QChar(property.name().at(0)).toUpper().toLatin1();
name[0] = firstChar.toLatin1();
name.prepend("on");
name.append("Changed");
propertiesAndSignals.append(name);
}
// Compare current properties and signals with the ones available for change type
QList<PropertyName> incompatibleProperties;
for (const auto &property : matProps) {
if (!propertiesAndSignals.contains(property.name()))
incompatibleProperties.append(property.name());
}
Utils::sort(incompatibleProperties);
// Create a dialog showing incompatible properties and signals
if (!incompatibleProperties.empty()) {
QString detailedText = tr("<b>Incompatible properties:</b><br>");
for (const auto &p : std::as_const(incompatibleProperties))
detailedText.append("- " + QString::fromUtf8(p) + "<br>");
detailedText.chop(QString("<br>").size());
QMessageBox msgBox;
msgBox.setTextFormat(Qt::RichText);
msgBox.setIcon(QMessageBox::Question);
msgBox.setWindowTitle(tr("Change Type"));
msgBox.setText(tr("Changing the type from %1 to %2 can't be done without removing incompatible properties.<br><br>%3")
.arg(m_selectedMaterial.simplifiedTypeName(), typeName, detailedText));
msgBox.setInformativeText(tr("Do you want to continue by removing incompatible properties?"));
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Ok);
if (msgBox.exec() == QMessageBox::Cancel)
return;
for (const auto &p : std::as_const(incompatibleProperties))
m_selectedMaterial.removeProperty(p);
}
if (m_selectedMaterial.isRootNode())
rewriterView->changeRootNodeType(metaInfo.typeName(), metaInfo.majorVersion(), metaInfo.minorVersion());
else
m_selectedMaterial.changeType(metaInfo.typeName(), metaInfo.majorVersion(), metaInfo.minorVersion());
});
}
void MaterialEditorContextObject::insertKeyframe(const QString &propertyName)
{
QTC_ASSERT(m_model && m_model->rewriterView(), return);
QTC_ASSERT(m_selectedMaterial.isValid(), return);
// Ideally we should not missuse the rewriterView
// If we add more code here we have to forward the material editor view
RewriterView *rewriterView = m_model->rewriterView();
QmlTimeline timeline = rewriterView->currentTimeline();
QTC_ASSERT(timeline.isValid(), return);
rewriterView->executeInTransaction("MaterialEditorContextObject::insertKeyframe", [&] {
timeline.insertKeyframe(m_selectedMaterial, propertyName.toUtf8());
});
}
int MaterialEditorContextObject::majorVersion() const
{
return m_majorVersion;
}
void MaterialEditorContextObject::setMajorVersion(int majorVersion)
{
if (m_majorVersion == majorVersion)
return;
m_majorVersion = majorVersion;
emit majorVersionChanged();
}
bool MaterialEditorContextObject::hasActiveTimeline() const
{
return m_hasActiveTimeline;
}
void MaterialEditorContextObject::setHasActiveTimeline(bool b)
{
if (b == m_hasActiveTimeline)
return;
m_hasActiveTimeline = b;
emit hasActiveTimelineChanged();
}
bool MaterialEditorContextObject::hasQuick3DImport() const
{
return m_hasQuick3DImport;
}
void MaterialEditorContextObject::setHasQuick3DImport(bool b)
{
if (b == m_hasQuick3DImport)
return;
m_hasQuick3DImport = b;
emit hasQuick3DImportChanged();
}
void MaterialEditorContextObject::setSelectedMaterial(const ModelNode &matNode)
{
m_selectedMaterial = matNode;
}
void MaterialEditorContextObject::setSpecificsUrl(const QUrl &newSpecificsUrl)
{
if (newSpecificsUrl == m_specificsUrl)
return;
m_specificsUrl = newSpecificsUrl;
emit specificsUrlChanged();
}
void MaterialEditorContextObject::setStateName(const QString &newStateName)
{
if (newStateName == m_stateName)
return;
m_stateName = newStateName;
emit stateNameChanged();
}
void MaterialEditorContextObject::setAllStateNames(const QStringList &allStates)
{
if (allStates == m_allStateNames)
return;
m_allStateNames = allStates;
emit allStateNamesChanged();
}
void MaterialEditorContextObject::setIsBaseState(bool newIsBaseState)
{
if (newIsBaseState == m_isBaseState)
return;
m_isBaseState = newIsBaseState;
emit isBaseStateChanged();
}
void MaterialEditorContextObject::setSelectionChanged(bool newSelectionChanged)
{
if (newSelectionChanged == m_selectionChanged)
return;
m_selectionChanged = newSelectionChanged;
emit selectionChangedChanged();
}
void MaterialEditorContextObject::setBackendValues(QQmlPropertyMap *newBackendValues)
{
if (newBackendValues == m_backendValues)
return;
m_backendValues = newBackendValues;
emit backendValuesChanged();
}
void MaterialEditorContextObject::setModel(Model *model)
{
m_model = model;
}
void MaterialEditorContextObject::triggerSelectionChanged()
{
setSelectionChanged(!m_selectionChanged);
}
void MaterialEditorContextObject::setHasAliasExport(bool hasAliasExport)
{
if (m_aliasExport == hasAliasExport)
return;
m_aliasExport = hasAliasExport;
emit hasAliasExportChanged();
}
void MaterialEditorContextObject::hideCursor()
{
if (QApplication::overrideCursor())
return;
QApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
if (QWidget *w = QApplication::activeWindow())
m_lastPos = QCursor::pos(w->screen());
}
void MaterialEditorContextObject::restoreCursor()
{
if (!QApplication::overrideCursor())
return;
QApplication::restoreOverrideCursor();
if (QWidget *w = QApplication::activeWindow())
QCursor::setPos(w->screen(), m_lastPos);
}
void MaterialEditorContextObject::holdCursorInPlace()
{
if (!QApplication::overrideCursor())
return;
if (QWidget *w = QApplication::activeWindow())
QCursor::setPos(w->screen(), m_lastPos);
}
int MaterialEditorContextObject::devicePixelRatio()
{
if (QWidget *w = QApplication::activeWindow())
return w->devicePixelRatio();
return 1;
}
QStringList MaterialEditorContextObject::allStatesForId(const QString &id)
{
if (m_model && m_model->rewriterView()) {
const QmlObjectNode node = m_model->rewriterView()->modelNodeForId(id);
if (node.isValid())
return node.allStateNames();
}
return {};
}
bool MaterialEditorContextObject::isBlocked(const QString &propName) const
{
if (!m_selectedMaterial.isValid())
return false;
if (!m_model || !m_model->rewriterView())
return false;
if (QmlObjectNode(m_selectedMaterial).isBlocked(propName.toUtf8()))
return true;
return false;
}
} // QmlDesigner

View File

@@ -0,0 +1,157 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <model.h>
#include <modelnode.h>
#include <QObject>
#include <QUrl>
#include <QQmlPropertyMap>
#include <QQmlComponent>
#include <QColor>
#include <QPoint>
#include <QMouseEvent>
namespace QmlDesigner {
class MaterialEditorContextObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QUrl specificsUrl READ specificsUrl WRITE setSpecificsUrl NOTIFY specificsUrlChanged)
Q_PROPERTY(QString stateName READ stateName WRITE setStateName NOTIFY stateNameChanged)
Q_PROPERTY(QStringList allStateNames READ allStateNames WRITE setAllStateNames NOTIFY allStateNamesChanged)
Q_PROPERTY(bool isBaseState READ isBaseState WRITE setIsBaseState NOTIFY isBaseStateChanged)
Q_PROPERTY(bool selectionChanged READ selectionChanged WRITE setSelectionChanged NOTIFY selectionChangedChanged)
Q_PROPERTY(int majorVersion READ majorVersion WRITE setMajorVersion NOTIFY majorVersionChanged)
Q_PROPERTY(bool hasAliasExport READ hasAliasExport NOTIFY hasAliasExportChanged)
Q_PROPERTY(bool hasActiveTimeline READ hasActiveTimeline NOTIFY hasActiveTimelineChanged)
Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged)
Q_PROPERTY(QQmlPropertyMap *backendValues READ backendValues WRITE setBackendValues NOTIFY backendValuesChanged)
public:
MaterialEditorContextObject(QObject *parent = nullptr);
QUrl specificsUrl() const { return m_specificsUrl; }
QString stateName() const { return m_stateName; }
QStringList allStateNames() const { return m_allStateNames; }
bool isBaseState() const { return m_isBaseState; }
bool selectionChanged() const { return m_selectionChanged; }
QQmlPropertyMap *backendValues() const { return m_backendValues; }
Q_INVOKABLE QString convertColorToString(const QVariant &color);
Q_INVOKABLE QColor colorFromString(const QString &colorString);
Q_INVOKABLE void changeTypeName(const QString &typeName);
Q_INVOKABLE void insertKeyframe(const QString &propertyName);
Q_INVOKABLE void hideCursor();
Q_INVOKABLE void restoreCursor();
Q_INVOKABLE void holdCursorInPlace();
Q_INVOKABLE int devicePixelRatio();
Q_INVOKABLE QStringList allStatesForId(const QString &id);
Q_INVOKABLE bool isBlocked(const QString &propName) const;
enum ToolBarAction {
ApplyToSelected = 0,
ApplyToSelectedAdd,
AddNewMaterial,
DeleteCurrentMaterial,
OpenMaterialBrowser
};
Q_ENUM(ToolBarAction)
int majorVersion() const;
void setMajorVersion(int majorVersion);
bool hasActiveTimeline() const;
void setHasActiveTimeline(bool b);
bool hasQuick3DImport() const;
void setHasQuick3DImport(bool b);
bool hasAliasExport() const { return m_aliasExport; }
void setSelectedMaterial(const ModelNode &matNode);
void setSpecificsUrl(const QUrl &newSpecificsUrl);
void setStateName(const QString &newStateName);
void setAllStateNames(const QStringList &allStates);
void setIsBaseState(bool newIsBaseState);
void setSelectionChanged(bool newSelectionChanged);
void setBackendValues(QQmlPropertyMap *newBackendValues);
void setModel(QmlDesigner::Model *model);
void triggerSelectionChanged();
void setHasAliasExport(bool hasAliasExport);
signals:
void specificsUrlChanged();
void stateNameChanged();
void allStateNamesChanged();
void isBaseStateChanged();
void selectionChangedChanged();
void backendValuesChanged();
void majorVersionChanged();
void hasAliasExportChanged();
void hasActiveTimelineChanged();
void hasQuick3DImportChanged();
private:
QUrl m_specificsUrl;
QString m_stateName;
QStringList m_allStateNames;
int m_majorVersion = 1;
QQmlPropertyMap *m_backendValues = nullptr;
QQmlComponent *m_qmlComponent = nullptr;
Model *m_model = nullptr;
QPoint m_lastPos;
bool m_isBaseState = false;
bool m_selectionChanged = false;
bool m_aliasExport = false;
bool m_hasActiveTimeline = false;
bool m_hasQuick3DImport = false;
ModelNode m_selectedMaterial;
};
} // QmlDesigner

View File

@@ -0,0 +1,346 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "materialeditorqmlbackend.h"
#include "propertyeditorvalue.h"
#include "materialeditortransaction.h"
#include "materialeditorcontextobject.h"
#include <qmldesignerconstants.h>
#include <qmltimeline.h>
#include <qmlobjectnode.h>
#include <nodemetainfo.h>
#include <variantproperty.h>
#include <bindingproperty.h>
#include <coreplugin/icore.h>
#include <utils/qtcassert.h>
#include <utils/algorithm.h>
#include <utils/fileutils.h>
#include <QDir>
#include <QFileInfo>
#include <QQuickImageProvider>
#include <QQuickItem>
#include <QQuickWidget>
#include <QVector2D>
#include <QVector3D>
#include <QVector4D>
static QObject *variantToQObject(const QVariant &value)
{
if (value.userType() == QMetaType::QObjectStar || value.userType() > QMetaType::User)
return *(QObject **)value.constData();
return nullptr;
}
namespace QmlDesigner {
class MaterialEditorImageProvider : public QQuickImageProvider
{
QPixmap m_previewPixmap;
public:
MaterialEditorImageProvider()
: QQuickImageProvider(Pixmap) {}
void setPixmap(const QPixmap &pixmap)
{
m_previewPixmap = pixmap;
}
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override
{
Q_UNUSED(requestedSize)
QPixmap pixmap{150, 150};
if (id == "preview") {
if (!m_previewPixmap.isNull())
pixmap = m_previewPixmap;
} else {
QString path = Core::ICore::resourcePath("qmldesigner/materialEditorQmlSources/images/" + id).toString();
pixmap = QPixmap{path};
}
if (size)
*size = pixmap.size();
return pixmap;
}
};
MaterialEditorQmlBackend::MaterialEditorQmlBackend(MaterialEditorView *materialEditor)
: m_view(new QQuickWidget)
, m_materialEditorTransaction(new MaterialEditorTransaction(materialEditor))
, m_contextObject(new MaterialEditorContextObject())
, m_materialEditorImageProvider(new MaterialEditorImageProvider())
{
m_view->setResizeMode(QQuickWidget::SizeRootObjectToView);
m_view->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
m_view->engine()->addImageProvider("materialEditor", m_materialEditorImageProvider);
m_contextObject->setBackendValues(&m_backendValuesPropertyMap);
m_contextObject->setModel(materialEditor->model());
context()->setContextObject(m_contextObject.data());
QObject::connect(&m_backendValuesPropertyMap, &DesignerPropertyMap::valueChanged,
materialEditor, &MaterialEditorView::changeValue);
}
MaterialEditorQmlBackend::~MaterialEditorQmlBackend()
{
}
PropertyName MaterialEditorQmlBackend::auxNamePostFix(const PropertyName &propertyName)
{
return propertyName + "__AUX";
}
QVariant MaterialEditorQmlBackend::properDefaultAuxiliaryProperties(const QmlObjectNode &qmlObjectNode,
const PropertyName &propertyName)
{
const ModelNode node = qmlObjectNode.modelNode();
const PropertyName auxName = propertyName;
if (node.hasAuxiliaryData(auxName))
return node.auxiliaryData(auxName);
return {};
}
void MaterialEditorQmlBackend::createPropertyEditorValue(const QmlObjectNode &qmlObjectNode,
const PropertyName &name,
const QVariant &value,
MaterialEditorView *materialEditor)
{
PropertyName propertyName(name);
propertyName.replace('.', '_');
auto valueObject = qobject_cast<PropertyEditorValue *>(variantToQObject(backendValuesPropertyMap().value(QString::fromUtf8(propertyName))));
if (!valueObject) {
valueObject = new PropertyEditorValue(&backendValuesPropertyMap());
QObject::connect(valueObject, &PropertyEditorValue::valueChanged, &backendValuesPropertyMap(), &DesignerPropertyMap::valueChanged);
QObject::connect(valueObject, &PropertyEditorValue::expressionChanged, materialEditor, &MaterialEditorView::changeExpression);
QObject::connect(valueObject, &PropertyEditorValue::exportPropertyAsAliasRequested, materialEditor, &MaterialEditorView::exportPropertyAsAlias);
QObject::connect(valueObject, &PropertyEditorValue::removeAliasExportRequested, materialEditor, &MaterialEditorView::removeAliasExport);
backendValuesPropertyMap().insert(QString::fromUtf8(propertyName), QVariant::fromValue(valueObject));
}
valueObject->setName(name);
valueObject->setModelNode(qmlObjectNode);
if (qmlObjectNode.propertyAffectedByCurrentState(name) && !(qmlObjectNode.modelNode().property(name).isBindingProperty()))
valueObject->setValue(qmlObjectNode.modelValue(name));
else
valueObject->setValue(value);
if (propertyName != "id" && qmlObjectNode.currentState().isBaseState()
&& qmlObjectNode.modelNode().property(propertyName).isBindingProperty()) {
valueObject->setExpression(qmlObjectNode.modelNode().bindingProperty(propertyName).expression());
} else {
if (qmlObjectNode.hasBindingProperty(name))
valueObject->setExpression(qmlObjectNode.expression(name));
else
valueObject->setExpression(qmlObjectNode.instanceValue(name).toString());
}
}
void MaterialEditorQmlBackend::setValue(const QmlObjectNode &, const PropertyName &name, const QVariant &value)
{
// Vector*D values need to be split into their subcomponents
if (value.type() == QVariant::Vector2D) {
const char *suffix[2] = {"_x", "_y"};
auto vecValue = value.value<QVector2D>();
for (int i = 0; i < 2; ++i) {
PropertyName subPropName(name.size() + 2, '\0');
subPropName.replace(0, name.size(), name);
subPropName.replace(name.size(), 2, suffix[i]);
auto propertyValue = qobject_cast<PropertyEditorValue *>(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(subPropName))));
if (propertyValue)
propertyValue->setValue(QVariant(vecValue[i]));
}
} else if (value.type() == QVariant::Vector3D) {
const char *suffix[3] = {"_x", "_y", "_z"};
auto vecValue = value.value<QVector3D>();
for (int i = 0; i < 3; ++i) {
PropertyName subPropName(name.size() + 2, '\0');
subPropName.replace(0, name.size(), name);
subPropName.replace(name.size(), 2, suffix[i]);
auto propertyValue = qobject_cast<PropertyEditorValue *>(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(subPropName))));
if (propertyValue)
propertyValue->setValue(QVariant(vecValue[i]));
}
} else if (value.type() == QVariant::Vector4D) {
const char *suffix[4] = {"_x", "_y", "_z", "_w"};
auto vecValue = value.value<QVector4D>();
for (int i = 0; i < 4; ++i) {
PropertyName subPropName(name.size() + 2, '\0');
subPropName.replace(0, name.size(), name);
subPropName.replace(name.size(), 2, suffix[i]);
auto propertyValue = qobject_cast<PropertyEditorValue *>(
variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(subPropName))));
if (propertyValue)
propertyValue->setValue(QVariant(vecValue[i]));
}
} else {
PropertyName propertyName = name;
propertyName.replace('.', '_');
auto propertyValue = qobject_cast<PropertyEditorValue *>(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(propertyName))));
if (propertyValue)
propertyValue->setValue(value);
}
}
QQmlContext *MaterialEditorQmlBackend::context() const
{
return m_view->rootContext();
}
MaterialEditorContextObject *MaterialEditorQmlBackend::contextObject() const
{
return m_contextObject.data();
}
QQuickWidget *MaterialEditorQmlBackend::widget() const
{
return m_view;
}
void MaterialEditorQmlBackend::setSource(const QUrl &url)
{
m_view->setSource(url);
}
Internal::QmlAnchorBindingProxy &MaterialEditorQmlBackend::backendAnchorBinding()
{
return m_backendAnchorBinding;
}
void MaterialEditorQmlBackend::updateMaterialPreview(const QPixmap &pixmap)
{
m_materialEditorImageProvider->setPixmap(pixmap);
QMetaObject::invokeMethod(m_view->rootObject(), "refreshPreview");
}
DesignerPropertyMap &MaterialEditorQmlBackend::backendValuesPropertyMap()
{
return m_backendValuesPropertyMap;
}
MaterialEditorTransaction *MaterialEditorQmlBackend::materialEditorTransaction() const
{
return m_materialEditorTransaction.data();
}
PropertyEditorValue *MaterialEditorQmlBackend::propertyValueForName(const QString &propertyName)
{
return qobject_cast<PropertyEditorValue *>(variantToQObject(backendValuesPropertyMap().value(propertyName)));
}
void MaterialEditorQmlBackend::setup(const QmlObjectNode &selectedMaterialNode, const QString &stateName,
const QUrl &qmlSpecificsFile, MaterialEditorView *materialEditor)
{
if (selectedMaterialNode.isValid()) {
m_contextObject->setModel(materialEditor->model());
const PropertyNameList propertyNames = selectedMaterialNode.modelNode().metaInfo().propertyNames();
for (const PropertyName &propertyName : propertyNames)
createPropertyEditorValue(selectedMaterialNode, propertyName, selectedMaterialNode.instanceValue(propertyName), materialEditor);
// model node
m_backendModelNode.setup(selectedMaterialNode.modelNode());
context()->setContextProperty("modelNodeBackend", &m_backendModelNode);
context()->setContextProperty("hasMaterial", QVariant(true));
// className
auto valueObject = qobject_cast<PropertyEditorValue *>(variantToQObject(
m_backendValuesPropertyMap.value(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY)));
if (!valueObject)
valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
valueObject->setName(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY);
valueObject->setModelNode(selectedMaterialNode.modelNode());
valueObject->setValue(m_backendModelNode.simplifiedTypeName());
QObject::connect(valueObject,
&PropertyEditorValue::valueChanged,
&backendValuesPropertyMap(),
&DesignerPropertyMap::valueChanged);
m_backendValuesPropertyMap.insert(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY,
QVariant::fromValue(valueObject));
// anchors
m_backendAnchorBinding.setup(selectedMaterialNode.modelNode());
context()->setContextProperties(
QVector<QQmlContext::PropertyPair>{
{{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)},
{{"transaction"}, QVariant::fromValue(m_materialEditorTransaction.data())}
}
);
contextObject()->setSpecificsUrl(qmlSpecificsFile);
contextObject()->setStateName(stateName);
QStringList stateNames = selectedMaterialNode.allStateNames();
stateNames.prepend("base state");
contextObject()->setAllStateNames(stateNames);
contextObject()->setSelectedMaterial(selectedMaterialNode);
contextObject()->setIsBaseState(selectedMaterialNode.isInBaseState());
contextObject()->setHasAliasExport(selectedMaterialNode.isAliasExported());
contextObject()->setHasActiveTimeline(QmlTimeline::hasActiveTimeline(selectedMaterialNode.view()));
contextObject()->setSelectionChanged(false);
NodeMetaInfo metaInfo = selectedMaterialNode.modelNode().metaInfo();
contextObject()->setMajorVersion(metaInfo.isValid() ? metaInfo.majorVersion() : -1);
} else {
context()->setContextProperty("hasMaterial", QVariant(false));
}
}
QString MaterialEditorQmlBackend::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();
}
void MaterialEditorQmlBackend::emitSelectionToBeChanged()
{
m_backendModelNode.emitSelectionToBeChanged();
}
void MaterialEditorQmlBackend::emitSelectionChanged()
{
m_backendModelNode.emitSelectionChanged();
}
void MaterialEditorQmlBackend::setValueforAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, const PropertyName &name)
{
const PropertyName propertyName = auxNamePostFix(name);
setValue(qmlObjectNode, propertyName, qmlObjectNode.modelNode().auxiliaryData(name));
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,93 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "designerpropertymap.h"
#include "qmlanchorbindingproxy.h"
#include "qmlmodelnodeproxy.h"
#include <nodemetainfo.h>
class PropertyEditorValue;
QT_BEGIN_NAMESPACE
class QQuickWidget;
QT_END_NAMESPACE
namespace QmlDesigner {
class MaterialEditorContextObject;
class MaterialEditorImageProvider;
class MaterialEditorTransaction;
class MaterialEditorView;
class MaterialEditorQmlBackend
{
Q_DISABLE_COPY(MaterialEditorQmlBackend)
public:
MaterialEditorQmlBackend(MaterialEditorView *materialEditor);
~MaterialEditorQmlBackend();
void setup(const QmlObjectNode &selectedMaterialNode, const QString &stateName, const QUrl &qmlSpecificsFile,
MaterialEditorView *materialEditor);
void setValue(const QmlObjectNode &fxObjectNode, const PropertyName &name, const QVariant &value);
QQmlContext *context() const;
MaterialEditorContextObject *contextObject() const;
QQuickWidget *widget() const;
void setSource(const QUrl &url);
Internal::QmlAnchorBindingProxy &backendAnchorBinding();
void updateMaterialPreview(const QPixmap &pixmap);
DesignerPropertyMap &backendValuesPropertyMap();
MaterialEditorTransaction *materialEditorTransaction() const;
PropertyEditorValue *propertyValueForName(const QString &propertyName);
static QString propertyEditorResourcesPath();
void emitSelectionToBeChanged();
void emitSelectionChanged();
void setValueforAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, const PropertyName &name);
private:
void createPropertyEditorValue(const QmlObjectNode &qmlObjectNode,
const PropertyName &name, const QVariant &value,
MaterialEditorView *materialEditor);
PropertyName auxNamePostFix(const PropertyName &propertyName);
QVariant properDefaultAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, const PropertyName &propertyName);
QQuickWidget *m_view = nullptr;
Internal::QmlAnchorBindingProxy m_backendAnchorBinding;
QmlModelNodeProxy m_backendModelNode;
DesignerPropertyMap m_backendValuesPropertyMap;
QScopedPointer<MaterialEditorTransaction> m_materialEditorTransaction;
QScopedPointer<MaterialEditorContextObject> m_contextObject;
MaterialEditorImageProvider *m_materialEditorImageProvider = nullptr;
};
} // namespace QmlDesigner

View File

@@ -0,0 +1,73 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "materialeditortransaction.h"
#include <QTimerEvent>
#include <QDebug>
namespace QmlDesigner {
MaterialEditorTransaction::MaterialEditorTransaction(QmlDesigner::MaterialEditorView *materialEditor)
: QObject(materialEditor),
m_materialEditor(materialEditor)
{
}
void MaterialEditorTransaction::start()
{
if (!m_materialEditor->model())
return;
if (m_rewriterTransaction.isValid())
m_rewriterTransaction.commit();
m_rewriterTransaction = m_materialEditor->beginRewriterTransaction(QByteArrayLiteral("MaterialEditorTransaction::start"));
m_timerId = startTimer(10000);
}
void MaterialEditorTransaction::end()
{
if (m_rewriterTransaction.isValid() && m_materialEditor->model()) {
killTimer(m_timerId);
m_rewriterTransaction.commit();
}
}
bool MaterialEditorTransaction::active() const
{
return m_rewriterTransaction.isValid();
}
void MaterialEditorTransaction::timerEvent(QTimerEvent *timerEvent)
{
if (timerEvent->timerId() != m_timerId)
return;
killTimer(timerEvent->timerId());
if (m_rewriterTransaction.isValid())
m_rewriterTransaction.commit();
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,53 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "materialeditorview.h"
namespace QmlDesigner {
class MaterialEditorTransaction : public QObject
{
Q_OBJECT
public:
MaterialEditorTransaction(QmlDesigner::MaterialEditorView *materialEditor);
Q_INVOKABLE void start();
Q_INVOKABLE void end();
Q_INVOKABLE bool active() const;
protected:
void timerEvent(QTimerEvent *event) override;
private:
QmlDesigner::MaterialEditorView *m_materialEditor = nullptr;
QmlDesigner::RewriterTransaction m_rewriterTransaction;
int m_timerId = -1;
};
} // namespace QmlDesigner

View File

@@ -0,0 +1,817 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "materialeditorview.h"
#include "materialeditorqmlbackend.h"
#include "materialeditorcontextobject.h"
#include "propertyeditorvalue.h"
#include "materialeditortransaction.h"
#include <qmldesignerconstants.h>
#include <qmltimeline.h>
#include <nodemetainfo.h>
#include <nodeproperty.h>
#include <nodelistproperty.h>
#include <nodeinstanceview.h>
#include <metainfo.h>
#include <rewritingexception.h>
#include <variantproperty.h>
#include <bindingproperty.h>
#include <theme.h>
#include <coreplugin/icore.h>
#include <coreplugin/messagebox.h>
#include <designmodewidget.h>
#include <qmldesignerplugin.h>
#include <utils/fileutils.h>
#include <utils/qtcassert.h>
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QQuickWidget>
#include <QQuickItem>
#include <QScopedPointer>
#include <QStackedWidget>
#include <QShortcut>
#include <QTimer>
namespace QmlDesigner {
MaterialEditorView::MaterialEditorView(QWidget *parent)
: AbstractView(parent)
, m_stackedWidget(new QStackedWidget(parent))
{
m_updateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F7), m_stackedWidget);
connect(m_updateShortcut, &QShortcut::activated, this, &MaterialEditorView::reloadQml);
m_stackedWidget->setStyleSheet(Theme::replaceCssColors(
QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"))));
m_stackedWidget->setMinimumWidth(250);
}
void MaterialEditorView::ensureMaterialLibraryNode()
{
if (!m_hasQuick3DImport)
return;
m_materialLibrary = modelNodeForId(Constants::MATERIAL_LIB_ID);
if (m_materialLibrary.isValid())
return;
const QList<ModelNode> materials = rootModelNode().subModelNodesOfType("QtQuick3D.Material");
if (materials.isEmpty())
return;
// create material library node
TypeName nodeType = rootModelNode().isSubclassOf("QtQuick3D.Node") ? "Quick3D.Node" : "QtQuick.Item";
NodeMetaInfo metaInfo = model()->metaInfo(nodeType);
m_materialLibrary = createModelNode(nodeType, metaInfo.majorVersion(), metaInfo.minorVersion());
m_materialLibrary.setIdWithoutRefactoring(Constants::MATERIAL_LIB_ID);
rootModelNode().defaultNodeListProperty().reparentHere(m_materialLibrary);
// move all materials to under material library node
for (const ModelNode &node : materials) {
// if material has no name, set name to id
QString matName = node.variantProperty("objectName").value().toString();
if (matName.isEmpty()) {
VariantProperty objNameProp = node.variantProperty("objectName");
objNameProp.setValue(node.id());
}
m_materialLibrary.defaultNodeListProperty().reparentHere(node);
}
}
MaterialEditorView::~MaterialEditorView()
{
qDeleteAll(m_qmlBackendHash);
}
// from material editor to model
void MaterialEditorView::changeValue(const QString &name)
{
PropertyName propertyName = name.toUtf8();
if (propertyName.isNull() || locked() || noValidSelection() || propertyName == "id"
|| propertyName == Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY) {
return;
}
if (propertyName == "objectName") {
renameMaterial(m_selectedMaterial, m_qmlBackEnd->propertyValueForName("objectName")->value().toString());
return;
}
PropertyName underscoreName(propertyName);
underscoreName.replace('.', '_');
PropertyEditorValue *value = m_qmlBackEnd->propertyValueForName(QString::fromLatin1(underscoreName));
if (!value)
return;
if (propertyName.endsWith("__AUX")) {
commitAuxValueToModel(propertyName, value->value());
return;
}
const NodeMetaInfo metaInfo = m_selectedMaterial.metaInfo();
QVariant castedValue;
if (metaInfo.isValid() && metaInfo.hasProperty(propertyName)) {
castedValue = metaInfo.propertyCastedValue(propertyName, value->value());
} else {
qWarning() << __FUNCTION__ << propertyName << "cannot be casted (metainfo)";
return;
}
if (value->value().isValid() && !castedValue.isValid()) {
qWarning() << __FUNCTION__ << propertyName << "not properly casted (metainfo)";
return;
}
bool propertyTypeUrl = false;
if (metaInfo.isValid() && metaInfo.hasProperty(propertyName)) {
if (metaInfo.propertyTypeName(propertyName) == "QUrl"
|| metaInfo.propertyTypeName(propertyName) == "url") {
// turn absolute local file paths into relative paths
propertyTypeUrl = true;
QString filePath = castedValue.toUrl().toString();
QFileInfo fi(filePath);
if (fi.exists() && fi.isAbsolute()) {
QDir fileDir(QFileInfo(model()->fileUrl().toLocalFile()).absolutePath());
castedValue = QUrl(fileDir.relativeFilePath(filePath));
}
}
}
if (name == "state" && castedValue.toString() == "base state")
castedValue = "";
if (castedValue.type() == QVariant::Color) {
QColor color = castedValue.value<QColor>();
QColor newColor = QColor(color.name());
newColor.setAlpha(color.alpha());
castedValue = QVariant(newColor);
}
if (!value->value().isValid() || (propertyTypeUrl && value->value().toString().isEmpty())) { // reset
removePropertyFromModel(propertyName);
} else {
// QVector*D(0, 0, 0) detects as null variant though it is valid value
if (castedValue.isValid()
&& (!castedValue.isNull() || castedValue.type() == QVariant::Vector2D
|| castedValue.type() == QVariant::Vector3D
|| castedValue.type() == QVariant::Vector4D)) {
commitVariantValueToModel(propertyName, castedValue);
}
}
requestPreviewRender();
}
static bool isTrueFalseLiteral(const QString &expression)
{
return (expression.compare("false", Qt::CaseInsensitive) == 0)
|| (expression.compare("true", Qt::CaseInsensitive) == 0);
}
void MaterialEditorView::changeExpression(const QString &propertyName)
{
PropertyName name = propertyName.toUtf8();
if (name.isNull() || locked() || noValidSelection())
return;
executeInTransaction("MaterialEditorView::changeExpression", [this, name] {
PropertyName underscoreName(name);
underscoreName.replace('.', '_');
QmlObjectNode qmlObjectNode(m_selectedMaterial);
PropertyEditorValue *value = m_qmlBackEnd->propertyValueForName(QString::fromLatin1(underscoreName));
if (!value) {
qWarning() << __FUNCTION__ << "no value for " << underscoreName;
return;
}
if (m_selectedMaterial.metaInfo().isValid() && m_selectedMaterial.metaInfo().hasProperty(name)) {
if (m_selectedMaterial.metaInfo().propertyTypeName(name) == "QColor") {
if (QColor(value->expression().remove('"')).isValid()) {
qmlObjectNode.setVariantProperty(name, QColor(value->expression().remove('"')));
return;
}
} else if (m_selectedMaterial.metaInfo().propertyTypeName(name) == "bool") {
if (isTrueFalseLiteral(value->expression())) {
if (value->expression().compare("true", Qt::CaseInsensitive) == 0)
qmlObjectNode.setVariantProperty(name, true);
else
qmlObjectNode.setVariantProperty(name, false);
return;
}
} else if (m_selectedMaterial.metaInfo().propertyTypeName(name) == "int") {
bool ok;
int intValue = value->expression().toInt(&ok);
if (ok) {
qmlObjectNode.setVariantProperty(name, intValue);
return;
}
} else if (m_selectedMaterial.metaInfo().propertyTypeName(name) == "qreal") {
bool ok;
qreal realValue = value->expression().toDouble(&ok);
if (ok) {
qmlObjectNode.setVariantProperty(name, realValue);
return;
}
} else if (m_selectedMaterial.metaInfo().propertyTypeName(name) == "QVariant") {
bool ok;
qreal realValue = value->expression().toDouble(&ok);
if (ok) {
qmlObjectNode.setVariantProperty(name, realValue);
return;
} else if (isTrueFalseLiteral(value->expression())) {
if (value->expression().compare("true", Qt::CaseInsensitive) == 0)
qmlObjectNode.setVariantProperty(name, true);
else
qmlObjectNode.setVariantProperty(name, false);
return;
}
}
}
if (value->expression().isEmpty()) {
value->resetValue();
return;
}
if (qmlObjectNode.expression(name) != value->expression() || !qmlObjectNode.propertyAffectedByCurrentState(name))
qmlObjectNode.setBindingProperty(name, value->expression());
requestPreviewRender();
}); // end of transaction
}
void MaterialEditorView::exportPropertyAsAlias(const QString &name)
{
if (name.isNull() || locked() || noValidSelection())
return;
executeInTransaction("MaterialEditorView::exportPopertyAsAlias", [this, name] {
const QString id = m_selectedMaterial.validId();
QString upperCasePropertyName = name;
upperCasePropertyName.replace(0, 1, upperCasePropertyName.at(0).toUpper());
QString aliasName = id + upperCasePropertyName;
aliasName.replace(".", ""); //remove all dots
PropertyName propertyName = aliasName.toUtf8();
if (rootModelNode().hasProperty(propertyName)) {
Core::AsynchronousMessageBox::warning(tr("Cannot Export Property as Alias"),
tr("Property %1 does already exist for root component.").arg(aliasName));
return;
}
rootModelNode().bindingProperty(propertyName).setDynamicTypeNameAndExpression("alias", id + "." + name);
});
}
void MaterialEditorView::removeAliasExport(const QString &name)
{
if (name.isNull() || locked() || noValidSelection())
return;
executeInTransaction("MaterialEditorView::removeAliasExport", [this, name] {
const QString id = m_selectedMaterial.validId();
const QList<BindingProperty> bindingProps = rootModelNode().bindingProperties();
for (const BindingProperty &property : bindingProps) {
if (property.expression() == (id + "." + name)) {
rootModelNode().removeProperty(property.name());
break;
}
}
});
}
bool MaterialEditorView::locked() const
{
return m_locked;
}
void MaterialEditorView::currentTimelineChanged(const ModelNode &)
{
m_qmlBackEnd->contextObject()->setHasActiveTimeline(QmlTimeline::hasActiveTimeline(this));
}
void MaterialEditorView::delayedResetView()
{
// TODO: it seems the delayed reset is not needed. Leaving it commented out for now just in case it
// turned out to be needed. Otherwise will be removed after a small testing period.
// if (m_timerId)
// killTimer(m_timerId);
// m_timerId = startTimer(50);
resetView();
}
void MaterialEditorView::timerEvent(QTimerEvent *timerEvent)
{
if (m_timerId == timerEvent->timerId())
resetView();
}
void MaterialEditorView::resetView()
{
if (!model())
return;
m_locked = true;
if (m_timerId)
killTimer(m_timerId);
setupQmlBackend();
if (m_qmlBackEnd)
m_qmlBackEnd->emitSelectionChanged();
QTimer::singleShot(0, this, &MaterialEditorView::requestPreviewRender);
m_locked = false;
if (m_timerId)
m_timerId = 0;
}
// static
QString MaterialEditorView::materialEditorResourcesPath()
{
#ifdef SHARE_QML_PATH
if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
return QLatin1String(SHARE_QML_PATH) + "/materialEditorQmlSources";
#endif
return Core::ICore::resourcePath("qmldesigner/materialEditorQmlSources").toString();
}
void MaterialEditorView::applyMaterialToSelectedModels(const ModelNode &material, bool add)
{
if (m_selectedModels.isEmpty())
return;
QTC_ASSERT(material.isValid(), return);
auto expToList = [](const QString &exp) {
QString copy = exp;
copy = copy.remove("[").remove("]");
QStringList tmp = copy.split(',', Qt::SkipEmptyParts);
for (QString &str : tmp)
str = str.trimmed();
return tmp;
};
auto listToExp = [](QStringList &stringList) {
if (stringList.size() > 1)
return QString("[" + stringList.join(",") + "]");
if (stringList.size() == 1)
return stringList.first();
return QString();
};
executeInTransaction("MaterialEditorView::applyMaterialToSelectedModels", [&] {
for (const ModelNode &node : std::as_const(m_selectedModels)) {
QmlObjectNode qmlObjNode(node);
if (add) {
QStringList matList = expToList(qmlObjNode.expression("materials"));
matList.append(material.id());
QString updatedExp = listToExp(matList);
qmlObjNode.setBindingProperty("materials", updatedExp);
} else {
qmlObjNode.setBindingProperty("materials", material.id());
}
}
});
}
void MaterialEditorView::handleToolBarAction(int action)
{
QTC_ASSERT(m_hasQuick3DImport, return);
switch (action) {
case MaterialEditorContextObject::ApplyToSelected: {
applyMaterialToSelectedModels(m_selectedMaterial);
break;
}
case MaterialEditorContextObject::ApplyToSelectedAdd: {
applyMaterialToSelectedModels(m_selectedMaterial, true);
break;
}
case MaterialEditorContextObject::AddNewMaterial: {
ensureMaterialLibraryNode();
executeInTransaction("MaterialEditorView:handleToolBarAction", [&] {
NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.DefaultMaterial");
ModelNode newMatNode = createModelNode("QtQuick3D.DefaultMaterial", metaInfo.majorVersion(),
metaInfo.minorVersion());
renameMaterial(newMatNode, "New Material");
m_materialLibrary.defaultNodeListProperty().reparentHere(newMatNode);
});
break;
}
case MaterialEditorContextObject::DeleteCurrentMaterial: {
if (m_selectedMaterial.isValid())
m_selectedMaterial.destroy();
break;
}
case MaterialEditorContextObject::OpenMaterialBrowser: {
QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialBrowser");
break;
}
}
}
void MaterialEditorView::setupQmlBackend()
{
QUrl qmlPaneUrl;
QUrl qmlSpecificsUrl;
if (m_selectedMaterial.isValid() && m_hasQuick3DImport) {
qmlPaneUrl = QUrl::fromLocalFile(materialEditorResourcesPath() + "/MaterialEditorPane.qml");
NodeMetaInfo metaInfo = m_selectedMaterial.metaInfo();
QDir importDir(metaInfo.importDirectoryPath() + Constants::QML_DESIGNER_SUBFOLDER);
QString typeName = QString::fromUtf8(metaInfo.typeName().split('.').constLast());
qmlSpecificsUrl = QUrl::fromLocalFile(importDir.absoluteFilePath(typeName + "Specifics.qml"));
} else {
qmlPaneUrl = QUrl::fromLocalFile(materialEditorResourcesPath() + "/EmptyMaterialEditorPane.qml");
}
MaterialEditorQmlBackend *currentQmlBackend = m_qmlBackendHash.value(qmlPaneUrl.toString());
QString currentStateName = currentState().isBaseState() ? currentState().name() : "invalid state";
if (!currentQmlBackend) {
currentQmlBackend = new MaterialEditorQmlBackend(this);
m_stackedWidget->addWidget(currentQmlBackend->widget());
m_qmlBackendHash.insert(qmlPaneUrl.toString(), currentQmlBackend);
currentQmlBackend->setup(m_selectedMaterial, currentStateName, qmlSpecificsUrl, this);
currentQmlBackend->setSource(qmlPaneUrl);
QObject::connect(currentQmlBackend->widget()->rootObject(), SIGNAL(toolBarAction(int)),
this, SLOT(handleToolBarAction(int)));
} else {
currentQmlBackend->setup(m_selectedMaterial, currentStateName, qmlSpecificsUrl, this);
}
currentQmlBackend->contextObject()->setHasQuick3DImport(m_hasQuick3DImport);
m_stackedWidget->setCurrentWidget(currentQmlBackend->widget());
m_qmlBackEnd = currentQmlBackend;
}
void MaterialEditorView::commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value)
{
m_locked = true;
executeInTransaction("MaterialEditorView:commitVariantValueToModel", [&] {
QmlObjectNode(m_selectedMaterial).setVariantProperty(propertyName, value);
});
m_locked = false;
}
void MaterialEditorView::commitAuxValueToModel(const PropertyName &propertyName, const QVariant &value)
{
m_locked = true;
PropertyName name = propertyName;
name.chop(5);
try {
if (value.isValid())
m_selectedMaterial.setAuxiliaryData(name, value);
else
m_selectedMaterial.removeAuxiliaryData(name);
}
catch (const Exception &e) {
e.showException();
}
m_locked = false;
}
void MaterialEditorView::removePropertyFromModel(const PropertyName &propertyName)
{
m_locked = true;
executeInTransaction("MaterialEditorView:removePropertyFromModel", [&] {
QmlObjectNode(m_selectedMaterial).removeProperty(propertyName);
});
m_locked = false;
}
bool MaterialEditorView::noValidSelection() const
{
QTC_ASSERT(m_qmlBackEnd, return true);
return !QmlObjectNode::isValidQmlObjectNode(m_selectedMaterial);
}
void MaterialEditorView::modelAttached(Model *model)
{
AbstractView::modelAttached(model);
m_locked = true;
m_hasQuick3DImport = model->hasImport("QtQuick3D");
ensureMaterialLibraryNode();
if (!m_setupCompleted) {
reloadQml();
m_setupCompleted = true;
}
resetView();
m_locked = false;
}
void MaterialEditorView::modelAboutToBeDetached(Model *model)
{
AbstractView::modelAboutToBeDetached(model);
m_qmlBackEnd->materialEditorTransaction()->end();
}
void MaterialEditorView::propertiesRemoved(const QList<AbstractProperty> &propertyList)
{
if (noValidSelection())
return;
for (const AbstractProperty &property : propertyList) {
ModelNode node(property.parentModelNode());
if (node.isRootNode())
m_qmlBackEnd->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedMaterial).isAliasExported());
if (node == m_selectedMaterial || QmlObjectNode(m_selectedMaterial).propertyChangeForCurrentState() == node) {
setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).instanceValue(property.name()));
}
}
}
void MaterialEditorView::variantPropertiesChanged(const QList<VariantProperty> &propertyList, PropertyChangeFlags /*propertyChange*/)
{
if (noValidSelection())
return;
bool changed = false;
for (const VariantProperty &property : propertyList) {
ModelNode node(property.parentModelNode());
if (node == m_selectedMaterial || QmlObjectNode(m_selectedMaterial).propertyChangeForCurrentState() == node) {
if (m_selectedMaterial.property(property.name()).isBindingProperty())
setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).instanceValue(property.name()));
else
setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).modelValue(property.name()));
changed = true;
}
}
if (changed)
requestPreviewRender();
}
void MaterialEditorView::bindingPropertiesChanged(const QList<BindingProperty> &propertyList, PropertyChangeFlags /*propertyChange*/)
{
if (noValidSelection())
return;
bool changed = false;
for (const BindingProperty &property : propertyList) {
ModelNode node(property.parentModelNode());
if (property.isAliasExport())
m_qmlBackEnd->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedMaterial).isAliasExported());
if (node == m_selectedMaterial || QmlObjectNode(m_selectedMaterial).propertyChangeForCurrentState() == node) {
if (QmlObjectNode(m_selectedMaterial).modelNode().property(property.name()).isBindingProperty())
setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).instanceValue(property.name()));
else
setValue(m_selectedMaterial, property.name(), QmlObjectNode(m_selectedMaterial).modelValue(property.name()));
changed = true;
}
}
if (changed)
requestPreviewRender();
}
void MaterialEditorView::auxiliaryDataChanged(const ModelNode &node, const PropertyName &name, const QVariant &)
{
if (noValidSelection() || !node.isSelected())
return;
m_qmlBackEnd->setValueforAuxiliaryProperties(m_selectedMaterial, name);
}
// request render image for the selected material node
void MaterialEditorView::requestPreviewRender()
{
if (m_selectedMaterial.isValid())
model()->nodeInstanceView()->previewImageDataForGenericNode(m_selectedMaterial, {});
}
bool MaterialEditorView::hasWidget() const
{
return true;
}
WidgetInfo MaterialEditorView::widgetInfo()
{
return createWidgetInfo(m_stackedWidget, nullptr, "MaterialEditor", WidgetInfo::RightPane, 0, tr("Material Editor"));
}
void MaterialEditorView::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
const QList<ModelNode> &lastSelectedNodeList)
{
m_selectedModels.clear();
for (const ModelNode &node : selectedNodeList) {
if (node.isSubclassOf("QtQuick3D.Model"))
m_selectedModels.append(node);
}
}
void MaterialEditorView::currentStateChanged(const ModelNode &node)
{
QmlModelState newQmlModelState(node);
Q_ASSERT(newQmlModelState.isValid());
delayedResetView();
}
void MaterialEditorView::instancePropertyChanged(const QList<QPair<ModelNode, PropertyName> > &propertyList)
{
if (!m_selectedMaterial.isValid() || !m_qmlBackEnd)
return;
m_locked = true;
for (const QPair<ModelNode, PropertyName> &propertyPair : propertyList) {
const ModelNode modelNode = propertyPair.first;
const QmlObjectNode qmlObjectNode(modelNode);
const PropertyName propertyName = propertyPair.second;
if (qmlObjectNode.isValid() && modelNode == m_selectedMaterial && qmlObjectNode.currentState().isValid()) {
const AbstractProperty property = modelNode.property(propertyName);
if (!modelNode.hasProperty(propertyName) || modelNode.property(property.name()).isBindingProperty())
setValue(modelNode, property.name(), qmlObjectNode.instanceValue(property.name()));
else
setValue(modelNode, property.name(), qmlObjectNode.modelValue(property.name()));
}
}
m_locked = false;
}
void MaterialEditorView::nodeTypeChanged(const ModelNode &node, const TypeName &, int, int)
{
if (node == m_selectedMaterial)
delayedResetView();
}
void MaterialEditorView::modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap)
{
if (node == m_selectedMaterial)
m_qmlBackEnd->updateMaterialPreview(pixmap);
}
void MaterialEditorView::importsChanged(const QList<Import> &addedImports, const QList<Import> &removedImports)
{
m_hasQuick3DImport = model()->hasImport("QtQuick3D");
m_qmlBackEnd->contextObject()->setHasQuick3DImport(m_hasQuick3DImport);
ensureMaterialLibraryNode(); // create the material lib if Quick3D import is added
resetView();
}
void MaterialEditorView::renameMaterial(ModelNode &material, const QString &newName)
{
QTC_ASSERT(material.isValid(), return);
executeInTransaction("MaterialEditorView:renameMaterial", [&] {
material.setIdWithRefactoring(generateIdFromName(newName));
VariantProperty objNameProp = material.variantProperty("objectName");
objNameProp.setValue(newName);
});
}
void MaterialEditorView::customNotification(const AbstractView *view, const QString &identifier,
const QList<ModelNode> &nodeList, const QList<QVariant> &data)
{
if (identifier == "selected_material_changed") {
m_selectedMaterial = nodeList.first();
QTimer::singleShot(0, this, &MaterialEditorView::resetView);
} else if (identifier == "apply_to_selected_triggered") {
applyMaterialToSelectedModels(nodeList.first(), data.first().toBool());
} else if (identifier == "rename_material") {
if (m_selectedMaterial == nodeList.first())
renameMaterial(m_selectedMaterial, data.first().toString());
} else if (identifier == "add_new_material") {
handleToolBarAction(MaterialEditorContextObject::AddNewMaterial);
}
}
// from model to material editor
void MaterialEditorView::setValue(const QmlObjectNode &qmlObjectNode, const PropertyName &name, const QVariant &value)
{
m_locked = true;
m_qmlBackEnd->setValue(qmlObjectNode, name, value);
requestPreviewRender();
m_locked = false;
}
void MaterialEditorView::reloadQml()
{
m_qmlBackendHash.clear();
while (QWidget *widget = m_stackedWidget->widget(0)) {
m_stackedWidget->removeWidget(widget);
delete widget;
}
m_qmlBackEnd = nullptr;
resetView();
}
// generate a unique camelCase id from a name
QString MaterialEditorView::generateIdFromName(const QString &name)
{
QString newId;
if (name.isEmpty()) {
newId = "material";
} else {
// convert to camel case
QStringList nameWords = name.split(" ");
nameWords[0] = nameWords[0].at(0).toLower() + nameWords[0].mid(1);
for (int i = 1; i < nameWords.size(); ++i)
nameWords[i] = nameWords[i].at(0).toUpper() + nameWords[i].mid(1);
newId = nameWords.join("");
// if id starts with a number prepend an underscore
if (newId.at(0).isDigit())
newId.prepend('_');
}
QRegularExpression rgx("\\d+$"); // matches a number at the end of a string
while (hasId(newId)) { // id exists
QRegularExpressionMatch match = rgx.match(newId);
if (match.hasMatch()) { // ends with a number, increment it
QString numStr = match.captured();
int num = numStr.toInt() + 1;
newId = newId.mid(0, match.capturedStart()) + QString::number(num);
} else {
newId.append('1');
}
}
return newId;
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,125 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <abstractview.h>
#include <QHash>
#include <QTimer>
QT_BEGIN_NAMESPACE
class QShortcut;
class QStackedWidget;
class QTimer;
QT_END_NAMESPACE
namespace QmlDesigner {
class ModelNode;
class MaterialEditorQmlBackend;
class MaterialEditorView : public AbstractView
{
Q_OBJECT
public:
MaterialEditorView(QWidget *parent = nullptr);
~MaterialEditorView() override;
bool hasWidget() const override;
WidgetInfo widgetInfo() override;
void selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
const QList<ModelNode> &lastSelectedNodeList) override;
void propertiesRemoved(const QList<AbstractProperty> &propertyList) override;
void modelAttached(Model *model) override;
void modelAboutToBeDetached(Model *model) override;
void variantPropertiesChanged(const QList<VariantProperty> &propertyList, PropertyChangeFlags propertyChange) override;
void bindingPropertiesChanged(const QList<BindingProperty> &propertyList, PropertyChangeFlags propertyChange) override;
void auxiliaryDataChanged(const ModelNode &node, const PropertyName &name, const QVariant &data) override;
void resetView();
void currentStateChanged(const ModelNode &node) override;
void instancePropertyChanged(const QList<QPair<ModelNode, PropertyName> > &propertyList) override;
void nodeTypeChanged(const ModelNode& node, const TypeName &type, int majorVersion, int minorVersion) override;
void modelNodePreviewPixmapChanged(const ModelNode &node, const QPixmap &pixmap) override;
void importsChanged(const QList<Import> &addedImports, const QList<Import> &removedImports) override;
void customNotification(const AbstractView *view, const QString &identifier,
const QList<ModelNode> &nodeList, const QList<QVariant> &data) override;
void changeValue(const QString &name);
void changeExpression(const QString &name);
void exportPropertyAsAlias(const QString &name);
void removeAliasExport(const QString &name);
bool locked() const;
void currentTimelineChanged(const ModelNode &node) override;
public slots:
void handleToolBarAction(int action);
protected:
void timerEvent(QTimerEvent *event) override;
void setValue(const QmlObjectNode &fxObjectNode, const PropertyName &name, const QVariant &value);
private:
static QString materialEditorResourcesPath();
void reloadQml();
QString generateIdFromName(const QString &name);
void ensureMaterialLibraryNode();
void requestPreviewRender();
void applyMaterialToSelectedModels(const ModelNode &material, bool add = false);
void delayedResetView();
void setupQmlBackend();
void commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value);
void commitAuxValueToModel(const PropertyName &propertyName, const QVariant &value);
void removePropertyFromModel(const PropertyName &propertyName);
void renameMaterial(ModelNode &material, const QString &newName);
bool noValidSelection() const;
ModelNode m_selectedMaterial;
ModelNode m_materialLibrary;
QShortcut *m_updateShortcut = nullptr;
int m_timerId = 0;
QStackedWidget *m_stackedWidget = nullptr;
QList<ModelNode> m_selectedModels;
QHash<QString, MaterialEditorQmlBackend *> m_qmlBackendHash;
MaterialEditorQmlBackend *m_qmlBackEnd = nullptr;
bool m_locked = false;
bool m_setupCompleted = false;
bool m_hasQuick3DImport = false;
};
} // namespace QmlDesigner

View File

@@ -42,10 +42,10 @@
#include <abstractview.h>
#include <invalididexception.h>
#include <rewritingexception.h>
#include <qmldesignerconstants.h>
#include <qmlitemnode.h>
#include <designeractionmanager.h>
#include <import.h>
#include <coreplugin/icore.h>
#include <qmlprojectmanager/qmlproject.h>
@@ -339,7 +339,8 @@ QList<ModelNode> NavigatorTreeModel::filteredList(const NodeListProperty &proper
if (filter) {
list.append(Utils::filtered(nameFilteredList, [] (const ModelNode &arg) {
const bool value = QmlItemNode::isValidQmlItemNode(arg) || NodeHints::fromModelNode(arg).visibleInNavigator();
const bool value = (QmlItemNode::isValidQmlItemNode(arg) || NodeHints::fromModelNode(arg).visibleInNavigator())
&& arg.id() != Constants::MATERIAL_LIB_ID;
return value;
}));
} else {
@@ -688,13 +689,20 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in
return;
bool validContainer = false;
bool showMatToCompInfo = false;
ModelNode targetNode = targetProperty.parentModelNode();
// don't allow dropping materials on any node but Models
QString itemType = QString::fromLatin1(itemLibraryEntry.typeName());
if (itemType.startsWith("QtQuick3D.") && itemType.endsWith("Material")
&& !targetNode.isSubclassOf("QtQuick3D.Model")) {
return;
}
QmlObjectNode newQmlObjectNode;
m_view->executeInTransaction("NavigatorTreeModel::handleItemLibraryItemDrop", [&] {
newQmlObjectNode = QmlItemNode::createQmlObjectNode(m_view, itemLibraryEntry, QPointF(), targetProperty, false);
ModelNode newModelNode = newQmlObjectNode.modelNode();
if (newModelNode.isValid()) {
ModelNode targetNode = targetProperty.parentModelNode();
ChooseFromPropertyListDialog *dialog = ChooseFromPropertyListDialog::createIfNeeded(
targetNode, newModelNode, Core::ICore::dialogParent());
if (dialog) {
@@ -728,17 +736,35 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in
}
delete dialog;
}
if (newModelNode.isSubclassOf("QtQuick3D.View3D")) {
const QList<ModelNode> models = newModelNode.subModelNodesOfType("QtQuick3D.Model");
QTC_ASSERT(models.size() == 1, return);
assignMaterialToModel(models.at(0));
} else if (newModelNode.isSubclassOf("QtQuick3D.Model")) {
assignMaterialToModel(newModelNode);
}
// dropping a material on a model
if (newModelNode.isSubclassOf("QtQuick3D.Material")
&& targetProperty.parentModelNode().isSubclassOf("QtQuick3D.Node")
&& targetProperty.parentModelNode().isComponent()) {
// Inserting materials under imported components is likely a mistake, so
// notify user with a helpful messagebox that suggests the correct action.
showMatToCompInfo = true;
&& targetNode.isSubclassOf("QtQuick3D.Model")) {
// parent material to material library and assign it to target model
ModelNode matLib = m_view->modelNodeForId(Constants::MATERIAL_LIB_ID);
QTC_ASSERT(matLib.isValid(), return);
VariantProperty objName = newModelNode.variantProperty("objectName");
objName.setValue("New Material");
BindingProperty matsProp = targetNode.bindingProperty("materials");
matsProp.setExpression(newModelNode.id());
matLib.defaultNodeListProperty().reparentHere(newModelNode);
return;
}
if (!validContainer) {
if (!showMatToCompInfo)
validContainer = NodeHints::fromModelNode(targetProperty.parentModelNode()).canBeContainerFor(newModelNode);
validContainer = NodeHints::fromModelNode(targetProperty.parentModelNode()).canBeContainerFor(newModelNode);
if (!validContainer)
newQmlObjectNode.destroy();
}
@@ -771,30 +797,6 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in
}
}
}
if (showMatToCompInfo) {
QMessageBox::StandardButton selectedButton = QMessageBox::information(
Core::ICore::dialogParent(),
QCoreApplication::translate("NavigatorTreeModel", "Warning"),
QCoreApplication::translate(
"NavigatorTreeModel",
"Inserting materials under imported 3D component nodes is not supported. "
"Materials used in imported 3D components have to be modified inside the component itself.\n\n"
"Would you like to go into component \"%1\"?")
.arg(targetProperty.parentModelNode().id()),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
if (selectedButton == QMessageBox::Yes) {
qint32 internalId = targetProperty.parentModelNode().internalId();
QTimer::singleShot(0, this, [internalId, this]() {
if (!m_view.isNull() && m_view->model()) {
ModelNode node = m_view->modelNodeForInternalId(internalId);
if (node.isValid() && node.isComponent())
DocumentManager::goIntoComponent(node);
}
});
}
}
}
}
@@ -1092,6 +1094,40 @@ ModelNode NavigatorTreeModel::createTextureNode(const NodeAbstractProperty &targ
return {};
}
// Add a material to a Quick3D.Model node
void NavigatorTreeModel::assignMaterialToModel(const ModelNode &node)
{
ModelNode matLib = m_view->modelNodeForId(Constants::MATERIAL_LIB_ID);
QTC_ASSERT(matLib.isValid(), return);
QTC_ASSERT(node.isSubclassOf("QtQuick3D.Model"), return);
const QList<ModelNode> materials = matLib.directSubModelNodes();
ModelNode material;
if (materials.size() > 0) {
for (const ModelNode &mat : materials) {
if (mat.isSubclassOf("QtQuick3D.Material")) {
material = mat;
break;
}
}
}
// if no valid material, create a new default material
if (!material.isValid()) {
NodeMetaInfo metaInfo = m_view->model()->metaInfo("QtQuick3D.DefaultMaterial");
material = m_view->createModelNode("QtQuick3D.DefaultMaterial", metaInfo.majorVersion(),
metaInfo.minorVersion());
VariantProperty matNameProp = material.variantProperty("objectName");
matNameProp.setValue("New Material");
material.validId();
matLib.defaultNodeListProperty().reparentHere(material);
}
BindingProperty modelMatsProp = node.bindingProperty("materials");
modelMatsProp.setExpression(material.id());
}
TypeName propertyType(const NodeAbstractProperty &property)
{
return property.parentModelNode().metaInfo().propertyTypeName(property.name());

View File

@@ -130,6 +130,7 @@ private:
bool dropAsImage3dTexture(const ModelNode &targetNode, const NodeAbstractProperty &targetProp,
const QString &imagePath, ModelNode &newNode, bool &outMoveNodesAfter);
ModelNode createTextureNode(const NodeAbstractProperty &targetProp, const QString &imagePath);
void assignMaterialToModel(const ModelNode &node);
QList<QPersistentModelIndex> nodesToPersistentIndex(const QList<ModelNode> &modelNodes);
void addImport(const QString &importName);
QList<ModelNode> filteredList(const NodeListProperty &property, bool filter, bool reverseOrder) const;

View File

@@ -24,12 +24,15 @@
****************************************************************************/
#include "propertyeditorvalue.h"
#include "variantproperty.h"
#include "documentmanager.h"
#include <abstractview.h>
#include <bindingproperty.h>
#include <designdocument.h>
#include <nodeproperty.h>
#include <nodelistproperty.h>
#include <nodemetainfo.h>
#include <nodeproperty.h>
#include <qmldesignerplugin.h>
#include <qmlobjectnode.h>
#include <designermcumanager.h>
@@ -40,6 +43,7 @@
#include <QRegularExpression>
#include <QUrl>
#include <QScopedPointer>
#include <assetslibrarymodel.h>
//using namespace QmlDesigner;
@@ -367,6 +371,18 @@ void PropertyEditorValue::setEnumeration(const QString &scope, const QString &na
setValueWithEmit(QVariant::fromValue(newEnumeration));
}
bool PropertyEditorValue::isSupportedDrop(const QString &path)
{
QString suffix = "*." + QFileInfo(path).suffix().toLower();
if (m_modelNode.isSubclassOf("QtQuick3D.Material") && nameAsQString().endsWith("Map"))
return QmlDesigner::AssetsLibraryModel::supportedImageSuffixes().contains(suffix);
// TODO: handle support for other object properties dnd here (like image source)
return false;
}
void PropertyEditorValue::exportPropertyAsAlias()
{
emit exportPropertyAsAliasRequested(nameAsQString());
@@ -499,6 +515,29 @@ bool PropertyEditorValue::idListReplace(int idx, const QString &value)
return true;
}
void PropertyEditorValue::commitDrop(const QString &path)
{
if (m_modelNode.isSubclassOf("QtQuick3D.Material") && nameAsQString().endsWith("Map")) {
// create a texture node
QmlDesigner::NodeMetaInfo metaInfo = m_modelNode.view()->model()->metaInfo("QtQuick3D.Texture");
QmlDesigner::ModelNode texture = m_modelNode.view()->createModelNode("QtQuick3D.Texture",
metaInfo.majorVersion(),
metaInfo.minorVersion());
texture.validId();
modelNode().view()->rootModelNode().defaultNodeListProperty().reparentHere(texture);
// TODO: group textures under 1 node (just like materials)
// set texture source
Utils::FilePath imagePath = Utils::FilePath::fromString(path);
Utils::FilePath currFilePath = QmlDesigner::DocumentManager::currentFilePath();
QmlDesigner::VariantProperty srcProp = texture.variantProperty("source");
srcProp.setValue(imagePath.relativePath(currFilePath).toUrl());
// assign the texture to the property
setExpressionWithEmit(texture.id());
}
}
QStringList PropertyEditorValue::generateStringList(const QString &string) const
{
QString copy = string;

View File

@@ -143,10 +143,12 @@ public:
Q_INVOKABLE bool idListAdd(const QString &value);
Q_INVOKABLE bool idListRemove(int idx);
Q_INVOKABLE bool idListReplace(int idx, const QString &value);
Q_INVOKABLE void commitDrop(const QString &path);
public slots:
void resetValue();
void setEnumeration(const QString &scope, const QString &name);
bool isSupportedDrop(const QString &path);
signals:
void valueChanged(const QString &name, const QVariant&);

View File

@@ -125,6 +125,7 @@ public:
void clearMetaInfoCache();
bool hasId(const QString &id) const;
bool hasImport(const QString &importUrl) const;
QString generateNewId(const QString &prefixName) const;
QString generateNewId(const QString &prefixName, const QString &fallbackPrefix) const;

View File

@@ -1462,6 +1462,13 @@ bool Model::hasId(const QString &id) const
return d->hasId(id);
}
bool Model::hasImport(const QString &importUrl) const
{
return Utils::anyOf(imports(), [&](const Import &import) {
return import.url() == importUrl;
});
}
static QString firstCharToLower(const QString &string)
{
QString resultString = string;

View File

@@ -42,6 +42,8 @@
#include <navigatorview.h>
#include <nodeinstanceview.h>
#include <propertyeditorview.h>
#include <materialeditorview.h>
#include <materialbrowserview.h>
#include <rewriterview.h>
#include <stateseditorview.h>
#include <texteditorview.h>
@@ -76,6 +78,8 @@ public:
ItemLibraryView itemLibraryView;
NavigatorView navigatorView;
PropertyEditorView propertyEditorView;
MaterialEditorView materialEditorView;
MaterialBrowserView materialBrowserView;
StatesEditorView statesEditorView;
std::vector<std::unique_ptr<AbstractView>> additionalViews;
@@ -92,7 +96,7 @@ ViewManager::ViewManager()
d->formEditorView.setGotoErrorCallback([this](int line, int column) {
d->textEditorView.gotoCursorPosition(line, column);
if (Internal::DesignModeWidget *designModeWidget = QmlDesignerPlugin::instance()->mainWidget())
designModeWidget->showInternalTextEditor();
designModeWidget->showDockWidget("TextEditor");
});
}
@@ -183,6 +187,8 @@ QList<AbstractView *> ViewManager::standardViews() const
&d->itemLibraryView,
&d->navigatorView,
&d->propertyEditorView,
&d->materialEditorView,
&d->materialBrowserView,
&d->statesEditorView,
&d->designerActionManagerView};
@@ -316,6 +322,8 @@ QList<WidgetInfo> ViewManager::widgetInfos() const
widgetInfoList.append(d->itemLibraryView.widgetInfo());
widgetInfoList.append(d->navigatorView.widgetInfo());
widgetInfoList.append(d->propertyEditorView.widgetInfo());
widgetInfoList.append(d->materialEditorView.widgetInfo());
widgetInfoList.append(d->materialBrowserView.widgetInfo());
widgetInfoList.append(d->statesEditorView.widgetInfo());
if (d->debugView.hasWidget())
widgetInfoList.append(d->debugView.widgetInfo());

View File

@@ -583,9 +583,9 @@ CrumbleBar *DesignModeWidget::crumbleBar() const
return m_crumbleBar;
}
void DesignModeWidget::showInternalTextEditor()
void DesignModeWidget::showDockWidget(const QString &objectName)
{
auto dockWidget = m_dockManager->findDockWidget("TextEditor");
auto dockWidget = m_dockManager->findDockWidget(objectName);
if (dockWidget)
dockWidget->toggleView(true);
}

View File

@@ -79,14 +79,14 @@ public:
void enableWidgets();
void disableWidgets();
CrumbleBar* crumbleBar() const;
void showInternalTextEditor();
CrumbleBar *crumbleBar() const;
void showDockWidget(const QString &objectName);
void determineWorkspaceToRestoreAtStartup();
static QWidget *createProjectExplorerWidget(QWidget *parent);
private: // functions
private:
enum InitializeStatus { NotInitialized, Initializing, Initialized };
void toolBarOnGoBackClicked();
@@ -101,7 +101,6 @@ private: // functions
void aboutToShowWorkspaces();
private: // variables
QPointer<QWidget> m_bottomSideBar;
Core::EditorToolBar *m_toolBar;
CrumbleBar *m_crumbleBar;

View File

@@ -84,6 +84,7 @@ const char QUICK_3D_ASSET_IMPORT_DATA_NAME[] = "_importdata.json";
const char QUICK_3D_ASSET_IMPORT_DATA_OPTIONS_KEY[] = "import_options";
const char QUICK_3D_ASSET_IMPORT_DATA_SOURCE_KEY[] = "source_scene";
const char DEFAULT_ASSET_IMPORT_FOLDER[] = "/asset_imports";
const char MATERIAL_LIB_ID[] = "__materialLibrary__";
const char MIME_TYPE_ITEM_LIBRARY_INFO[] = "application/vnd.qtdesignstudio.itemlibraryinfo";
const char MIME_TYPE_ASSETS[] = "application/vnd.qtdesignstudio.assets";

View File

@@ -488,7 +488,7 @@ Project {
"componentcore/formatoperation.h",
"componentcore/layoutingridlayout.cpp",
"componentcore/layoutingridlayout.h",
"componentcore/theme.cpp",
"componentcore/theme.cpp",
"componentcore/theme.h",
"componentcore/modelnodecontextmenu.cpp",
"componentcore/modelnodecontextmenu.h",
@@ -675,6 +675,20 @@ Project {
"itemlibrary/itemlibrarywidget.h",
"itemlibrary/itemlibraryiconimageprovider.cpp",
"itemlibrary/itemlibraryiconimageprovider.h",
"materialbrowser/materialbrowsermodel.cpp",
"materialbrowser/materialbrowsermodel.h",
"materialbrowser/materialbrowserview.cpp",
"materialbrowser/materialbrowserview.h",
"materialbrowser/materialbrowserwidget.cpp",
"materialbrowser/materialbrowserwidget.h",
"materialeditor/materialeditorcontextobject.cpp",
"materialeditor/materialeditorcontextobject.h",
"materialeditor/materialeditorqmlbackend.cpp",
"materialeditor/materialeditorqmlbackend.h",
"materialeditor/materialeditortransaction.cpp",
"materialeditor/materialeditortransaction.h",
"materialeditor/materialeditorview.cpp",
"materialeditor/materialeditorview.h",
"navigator/iconcheckboxitemdelegate.cpp",
"navigator/iconcheckboxitemdelegate.h",
"navigator/nameitemdelegate.cpp",