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

@@ -55,6 +55,20 @@ private:
qint32 m_renderItemId; qint32 m_renderItemId;
}; };
inline bool operator==(const RequestModelNodePreviewImageCommand &first,
const RequestModelNodePreviewImageCommand &second)
{
return first.instanceId() == second.instanceId()
&& first.size() == second.size()
&& first.componentPath() == second.componentPath()
&& first.renderItemId() == second.renderItemId();
}
inline size_t qHash(const RequestModelNodePreviewImageCommand &key, size_t seed)
{
return qHashMulti(seed, key.instanceId(), key.size(), key.componentPath(), key.renderItemId());
}
QDataStream &operator<<(QDataStream &out, const RequestModelNodePreviewImageCommand &command); QDataStream &operator<<(QDataStream &out, const RequestModelNodePreviewImageCommand &command);
QDataStream &operator>>(QDataStream &in, RequestModelNodePreviewImageCommand &command); QDataStream &operator>>(QDataStream &in, RequestModelNodePreviewImageCommand &command);

View File

@@ -13,6 +13,7 @@
<file>mockfiles/images/directional@2x.png</file> <file>mockfiles/images/directional@2x.png</file>
<file>mockfiles/images/point.png</file> <file>mockfiles/images/point.png</file>
<file>mockfiles/images/point@2x.png</file> <file>mockfiles/images/point@2x.png</file>
<file>mockfiles/images/floor_tex.png</file>
<file>mockfiles/images/spot.png</file> <file>mockfiles/images/spot.png</file>
<file>mockfiles/images/spot@2x.png</file> <file>mockfiles/images/spot@2x.png</file>
<file>mockfiles/qt5/AdjustableArrow.qml</file> <file>mockfiles/qt5/AdjustableArrow.qml</file>

View File

@@ -15,6 +15,7 @@
<file>mockfiles/images/directional@2x.png</file> <file>mockfiles/images/directional@2x.png</file>
<file>mockfiles/images/point.png</file> <file>mockfiles/images/point.png</file>
<file>mockfiles/images/point@2x.png</file> <file>mockfiles/images/point@2x.png</file>
<file>mockfiles/images/floor_tex.png</file>
<file>mockfiles/images/spot.png</file> <file>mockfiles/images/spot.png</file>
<file>mockfiles/images/spot@2x.png</file> <file>mockfiles/images/spot@2x.png</file>
<file>mockfiles/qt6/AdjustableArrow.qml</file> <file>mockfiles/qt6/AdjustableArrow.qml</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -45,20 +45,48 @@ View3D {
Node { Node {
DirectionalLight { DirectionalLight {
eulerRotation.x: -30 shadowMapQuality: Light.ShadowMapQualityMedium
eulerRotation.y: -30 shadowFilter: 20
shadowFactor: 21
castsShadow: true
eulerRotation.x: -26
eulerRotation.y: -57
} }
PerspectiveCamera { PerspectiveCamera {
y: 125.331
z: 120 z: 120
clipFar: 1000 eulerRotation.x: -31
clipNear: 1 clipNear: 1
clipFar: 1000
} }
Model { Model {
id: model id: model
y: 50
source: "#Sphere" source: "#Sphere"
materials: previewMaterial materials: previewMaterial
} }
Model {
id: floorModel
source: "#Rectangle"
scale.y: 8
scale.x: 8
eulerRotation.x: -90
materials: floorMaterial
DefaultMaterial {
id: floorMaterial
diffuseMap: floorTex
Texture {
id: floorTex
source: "../images/floor_tex.png"
scaleU: floorModel.scale.x
scaleV: floorModel.scale.y
}
}
}
} }
} }

View File

@@ -45,21 +45,49 @@ View3D {
Node { Node {
DirectionalLight { DirectionalLight {
eulerRotation.x: -30 shadowMapQuality: Light.ShadowMapQualityMedium
eulerRotation.y: -30 shadowFilter: 20
shadowFactor: 21
castsShadow: true
eulerRotation.x: -26
eulerRotation.y: -57
} }
PerspectiveCamera { PerspectiveCamera {
y: 125.331
z: 120 z: 120
clipFar: 1000 eulerRotation.x: -31
clipNear: 1 clipNear: 1
clipFar: 1000
} }
Model { Model {
id: model id: model
readonly property bool _edit3dLocked: true // Make this non-pickable readonly property bool _edit3dLocked: true // Make this non-pickable
y: 50
source: "#Sphere" source: "#Sphere"
materials: previewMaterial materials: previewMaterial
} }
Model {
id: floorModel
source: "#Rectangle"
scale.y: 8
scale.x: 8
eulerRotation.x: -90
materials: floorMaterial
DefaultMaterial {
id: floorMaterial
diffuseMap: floorTex
Texture {
id: floorTex
source: "../images/floor_tex.png"
scaleU: floorModel.scale.x
scaleV: floorModel.scale.y
}
}
}
} }
} }

View File

@@ -1030,27 +1030,32 @@ void Qt5InformationNodeInstanceServer::doRender3DEditView()
void Qt5InformationNodeInstanceServer::renderModelNodeImageView() void Qt5InformationNodeInstanceServer::renderModelNodeImageView()
{ {
if (!m_renderModelNodeImageViewTimer.isActive()) if (!m_renderModelNodeImageViewTimer.isActive())
m_renderModelNodeImageViewTimer.start(0); m_renderModelNodeImageViewTimer.start(17);
} }
void Qt5InformationNodeInstanceServer::doRenderModelNodeImageView() void Qt5InformationNodeInstanceServer::doRenderModelNodeImageView()
{ {
// This crashes on Qt 6.0.x due to QtQuick3D issue, so the preview generation is disabled // This crashes on Qt 6.0.x due to QtQuick3D issue, so the preview generation is disabled
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) || QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) || QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
RequestModelNodePreviewImageCommand cmd = *m_modelNodePreviewImageCommands.begin();
ServerNodeInstance instance; ServerNodeInstance instance;
if (m_modelNodePreviewImageCommand.renderItemId() >= 0) if (cmd.renderItemId() >= 0)
instance = instanceForId(m_modelNodePreviewImageCommand.renderItemId()); instance = instanceForId(cmd.renderItemId());
else else
instance = instanceForId(m_modelNodePreviewImageCommand.instanceId()); instance = instanceForId(cmd.instanceId());
if (instance.isSubclassOf("QQuick3DObject")) if (instance.isSubclassOf("QQuick3DObject"))
doRenderModelNode3DImageView(); doRenderModelNode3DImageView(cmd);
else if (instance.isSubclassOf("QQuickItem")) else if (instance.isSubclassOf("QQuickItem"))
doRenderModelNode2DImageView(); doRenderModelNode2DImageView(cmd);
m_modelNodePreviewImageCommands.remove(cmd);
if (!m_modelNodePreviewImageCommands.isEmpty())
m_renderModelNodeImageViewTimer.start(17);
#endif #endif
} }
void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView() void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView(const RequestModelNodePreviewImageCommand &cmd)
{ {
#ifdef QUICK3D_MODULE #ifdef QUICK3D_MODULE
if (m_modelNode3DImageViewData.rootItem) { if (m_modelNode3DImageViewData.rootItem) {
@@ -1059,19 +1064,19 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView()
m_modelNode3DImageViewData.contentItem = getContentItemForRendering(m_modelNode3DImageViewData.rootItem); m_modelNode3DImageViewData.contentItem = getContentItemForRendering(m_modelNode3DImageViewData.rootItem);
QImage renderImage; QImage renderImage;
if (m_modelNodePreviewImageCache.contains(m_modelNodePreviewImageCommand.componentPath())) { if (m_modelNodePreviewImageCache.contains(cmd.componentPath())) {
renderImage = m_modelNodePreviewImageCache[m_modelNodePreviewImageCommand.componentPath()]; renderImage = m_modelNodePreviewImageCache[cmd.componentPath()];
} else { } else {
bool createdFromComponent = false; bool createdFromComponent = false;
QObject *instanceObj = nullptr; QObject *instanceObj = nullptr;
ServerNodeInstance instance = instanceForId(m_modelNodePreviewImageCommand.instanceId()); ServerNodeInstance instance = instanceForId(cmd.instanceId());
if (!m_modelNodePreviewImageCommand.componentPath().isEmpty() if (!cmd.componentPath().isEmpty()
&& instance.isSubclassOf("QQuick3DNode")) { && instance.isSubclassOf("QQuick3DNode")) {
// Create a new instance for Node components, as using Nodes in multiple // Create a new instance for Node components, as using Nodes in multiple
// import scenes simultaneously isn't supported. And even if it was, we still // import scenes simultaneously isn't supported. And even if it was, we still
// wouldn't want the children of the Node to appear in the preview. // wouldn't want the children of the Node to appear in the preview.
QQmlComponent component(engine()); QQmlComponent component(engine());
component.loadUrl(QUrl::fromLocalFile(m_modelNodePreviewImageCommand.componentPath())); component.loadUrl(QUrl::fromLocalFile(cmd.componentPath()));
instanceObj = qobject_cast<QQuick3DObject *>(component.create()); instanceObj = qobject_cast<QQuick3DObject *>(component.create());
if (!instanceObj) { if (!instanceObj) {
qWarning() << "Could not create preview component: " << component.errors(); qWarning() << "Could not create preview component: " << component.errors();
@@ -1081,7 +1086,7 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView()
} else { } else {
instanceObj = instance.internalObject(); instanceObj = instance.internalObject();
} }
QSize renderSize = m_modelNodePreviewImageCommand.size(); QSize renderSize = cmd.size();
if (Internal::QuickItemNodeInstance::unifiedRenderPathOrQt6()) { if (Internal::QuickItemNodeInstance::unifiedRenderPathOrQt6()) {
// Requested size is already adjusted for target pixel ratio, so we have to adjust // Requested size is already adjusted for target pixel ratio, so we have to adjust
// back if ratio is not default for our window. // back if ratio is not default for our window.
@@ -1138,13 +1143,13 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView()
if (createdFromComponent) { if (createdFromComponent) {
// If component changes, puppet will need a reset anyway, so we can cache the image // If component changes, puppet will need a reset anyway, so we can cache the image
m_modelNodePreviewImageCache.insert(m_modelNodePreviewImageCommand.componentPath(), m_modelNodePreviewImageCache.insert(cmd.componentPath(),
renderImage); renderImage);
delete instanceObj; delete instanceObj;
} }
} }
// Key number is selected so that it is unlikely to conflict other ImageContainer use. // Key number is selected so that it is unlikely to conflict other ImageContainer use.
ImageContainer imgContainer(m_modelNodePreviewImageCommand.instanceId(), {}, 2100000001); ImageContainer imgContainer(cmd.instanceId(), {}, 2100000001 + cmd.instanceId());
imgContainer.setImage(renderImage); imgContainer.setImage(renderImage);
// send the rendered image to creator process // send the rendered image to creator process
@@ -1175,23 +1180,23 @@ static QRectF itemBoundingRect(QQuickItem *item)
return itemRect; return itemRect;
} }
void Qt5InformationNodeInstanceServer::doRenderModelNode2DImageView() void Qt5InformationNodeInstanceServer::doRenderModelNode2DImageView(const RequestModelNodePreviewImageCommand &cmd)
{ {
if (m_modelNode2DImageViewData.rootItem) { if (m_modelNode2DImageViewData.rootItem) {
if (!m_modelNode2DImageViewData.contentItem) if (!m_modelNode2DImageViewData.contentItem)
m_modelNode2DImageViewData.contentItem = getContentItemForRendering(m_modelNode2DImageViewData.rootItem); m_modelNode2DImageViewData.contentItem = getContentItemForRendering(m_modelNode2DImageViewData.rootItem);
// Key number is the same as in 3D case as they produce image for same purpose // Key number is the same as in 3D case as they produce image for same purpose
auto imgContainer = ImageContainer(m_modelNodePreviewImageCommand.instanceId(), {}, 2100000001); auto imgContainer = ImageContainer(cmd.instanceId(), {}, 2100000001 + cmd.instanceId());
QImage renderImage; QImage renderImage;
if (m_modelNodePreviewImageCache.contains(m_modelNodePreviewImageCommand.componentPath())) { if (m_modelNodePreviewImageCache.contains(cmd.componentPath())) {
renderImage = m_modelNodePreviewImageCache[m_modelNodePreviewImageCommand.componentPath()]; renderImage = m_modelNodePreviewImageCache[cmd.componentPath()];
} else { } else {
QQuickItem *instanceItem = nullptr; QQuickItem *instanceItem = nullptr;
if (!m_modelNodePreviewImageCommand.componentPath().isEmpty()) { if (!cmd.componentPath().isEmpty()) {
QQmlComponent component(engine()); QQmlComponent component(engine());
component.loadUrl(QUrl::fromLocalFile(m_modelNodePreviewImageCommand.componentPath())); component.loadUrl(QUrl::fromLocalFile(cmd.componentPath()));
instanceItem = qobject_cast<QQuickItem *>(component.create()); instanceItem = qobject_cast<QQuickItem *>(component.create());
if (!instanceItem) { if (!instanceItem) {
qWarning() << "Could not create preview component: " << component.errors(); qWarning() << "Could not create preview component: " << component.errors();
@@ -1207,7 +1212,7 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode2DImageView()
// Some component may expect to always be shown at certain size, so their layouts may // Some component may expect to always be shown at certain size, so their layouts may
// not support scaling, so let's always render at the default size if item has one and // not support scaling, so let's always render at the default size if item has one and
// scale the resulting image instead. // scale the resulting image instead.
QSize finalSize = m_modelNodePreviewImageCommand.size(); QSize finalSize = cmd.size();
QRectF renderRect = itemBoundingRect(instanceItem); QRectF renderRect = itemBoundingRect(instanceItem);
QSize renderSize = renderRect.size().toSize(); QSize renderSize = renderRect.size().toSize();
if (renderSize.isEmpty()) { if (renderSize.isEmpty()) {
@@ -1247,7 +1252,7 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode2DImageView()
delete instanceItem; delete instanceItem;
// If component changes, puppet will need a reset anyway, so we can cache the image // If component changes, puppet will need a reset anyway, so we can cache the image
m_modelNodePreviewImageCache.insert(m_modelNodePreviewImageCommand.componentPath(), renderImage); m_modelNodePreviewImageCache.insert(cmd.componentPath(), renderImage);
} }
if (!renderImage.isNull()) { if (!renderImage.isNull()) {
@@ -2229,7 +2234,7 @@ void Qt5InformationNodeInstanceServer::view3DAction(const View3DActionCommand &c
void Qt5InformationNodeInstanceServer::requestModelNodePreviewImage(const RequestModelNodePreviewImageCommand &command) void Qt5InformationNodeInstanceServer::requestModelNodePreviewImage(const RequestModelNodePreviewImageCommand &command)
{ {
m_modelNodePreviewImageCommand = command; m_modelNodePreviewImageCommands.insert(command);
renderModelNodeImageView(); renderModelNodeImageView();
} }

View File

@@ -139,8 +139,8 @@ private:
void doRender3DEditView(); void doRender3DEditView();
void renderModelNodeImageView(); void renderModelNodeImageView();
void doRenderModelNodeImageView(); void doRenderModelNodeImageView();
void doRenderModelNode3DImageView(); void doRenderModelNode3DImageView(const RequestModelNodePreviewImageCommand &cmd);
void doRenderModelNode2DImageView(); void doRenderModelNode2DImageView(const RequestModelNodePreviewImageCommand &cmd);
void updateLockedAndHiddenStates(const QSet<ServerNodeInstance> &instances); void updateLockedAndHiddenStates(const QSet<ServerNodeInstance> &instances);
void handleInputEvents(); void handleInputEvents();
void resolveImportSupport(); void resolveImportSupport();
@@ -159,7 +159,7 @@ private:
RenderViewData m_modelNode2DImageViewData; RenderViewData m_modelNode2DImageViewData;
bool m_editView3DSetupDone = false; bool m_editView3DSetupDone = false;
RequestModelNodePreviewImageCommand m_modelNodePreviewImageCommand; QSet<RequestModelNodePreviewImageCommand> m_modelNodePreviewImageCommands;
QHash<QString, QImage> m_modelNodePreviewImageCache; QHash<QString, QImage> m_modelNodePreviewImageCache;
QSet<QObject *> m_view3Ds; QSet<QObject *> m_view3Ds;
QMultiHash<QObject *, QObject *> m_3DSceneMap; // key: scene root, value: node QMultiHash<QObject *, QObject *> m_3DSceneMap; // key: scene root, value: node

View File

@@ -442,10 +442,12 @@ Item {
width: parent.width - addAssetButton.width - 5 width: parent.width - addAssetButton.width - 5
} }
PlusButton { IconButton {
id: addAssetButton id: addAssetButton
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
tooltip: qsTr("Add a new asset to the project.") tooltip: qsTr("Add a new asset to the project.")
icon: StudioTheme.Constants.plus
buttonSize: parent.height
onClicked: rootView.handleAddAsset() onClicked: rootView.handleAddAsset()
} }

View File

@@ -218,12 +218,15 @@ Item {
SearchBox { SearchBox {
id: searchBox id: searchBox
width: parent.width - addAssetButton.width - 5 width: parent.width - addModuleButton.width - 5
} }
PlusButton { IconButton {
id: addAssetButton id: addModuleButton
anchors.verticalCenter: parent.verticalCenter
tooltip: qsTr("Add a module.") tooltip: qsTr("Add a module.")
icon: StudioTheme.Constants.plus
buttonSize: parent.height
onClicked: isAddModuleView = true onClicked: isAddModuleView = true
} }

View File

@@ -0,0 +1,221 @@
/****************************************************************************
**
** 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.
**
****************************************************************************/
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuickDesignerTheme 1.0
import HelperWidgets 2.0
import StudioControls 1.0 as StudioControls
import StudioTheme 1.0 as StudioTheme
Item {
id: root
readonly property int cellWidth: 100
readonly property int cellHeight: 120
property var currentMaterial: null
property int currentMaterialIdx: 0
// Called also from C++ to close context menu on focus out
function closeContextMenu()
{
contextMenu.close()
}
// Called from C++ to refresh a preview material after it changes
function refreshPreview(idx)
{
var item = gridRepeater.itemAt(idx);
if (item)
item.refreshPreview();
}
// Called from C++
function clearSearchFilter()
{
searchBox.clearSearchFilter();
}
MouseArea {
id: rootMouseArea
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
root.currentMaterial = null
contextMenu.popup()
}
}
Connections {
target: materialBrowserModel
function onSelectedIndexChanged() {
// commit rename upon changing selection
var item = gridRepeater.itemAt(currentMaterialIdx);
if (item)
item.commitRename();
currentMaterialIdx = materialBrowserModel.selectedIndex;
}
}
StudioControls.Menu {
id: contextMenu
closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside
StudioControls.MenuItem {
text: qsTr("Apply to selected (replace)")
enabled: currentMaterial
onTriggered: materialBrowserModel.applyToSelected(currentMaterial.materialInternalId, false)
}
StudioControls.MenuItem {
text: qsTr("Apply to selected (add)")
enabled: currentMaterial
onTriggered: materialBrowserModel.applyToSelected(currentMaterial.materialInternalId, true)
}
StudioControls.MenuItem {
text: qsTr("Rename")
enabled: currentMaterial
onTriggered: {
var item = gridRepeater.itemAt(currentMaterialIdx);
if (item)
item.startRename();
}
}
StudioControls.MenuItem {
text: qsTr("Delete")
enabled: currentMaterial
onTriggered: materialBrowserModel.deleteMaterial(currentMaterial.materialInternalId)
}
StudioControls.MenuSeparator {}
StudioControls.MenuItem {
text: qsTr("New Material")
onTriggered: materialBrowserModel.addNewMaterial()
}
}
Column {
id: col
y: 5
spacing: 5
Row {
width: root.width
SearchBox {
id: searchBox
width: root.width - addMaterialButton.width
}
IconButton {
id: addMaterialButton
tooltip: qsTr("Add a material.")
icon: StudioTheme.Constants.plus
anchors.verticalCenter: parent.verticalCenter
buttonSize: searchBox.height
onClicked: materialBrowserModel.addNewMaterial()
}
}
Text {
text: qsTr("No match found.");
color: StudioTheme.Values.themeTextColor
font.pixelSize: StudioTheme.Values.baseFontSize
leftPadding: 10
visible: materialBrowserModel.hasQuick3DImport && materialBrowserModel.isEmpty && !searchBox.isEmpty()
}
Text {
text: qsTr("No materials yet.");
color: StudioTheme.Values.themeTextColor
font.pixelSize: StudioTheme.Values.mediumFontSize
topPadding: 30
anchors.horizontalCenter: parent.horizontalCenter
visible: materialBrowserModel.hasQuick3DImport && materialBrowserModel.isEmpty && searchBox.isEmpty()
}
Text {
text: qsTr("Add QtQuick3D module using the Components view to enable the Material Browser.");
color: StudioTheme.Values.themeTextColor
font.pixelSize: StudioTheme.Values.mediumFontSize
topPadding: 30
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
width: root.width
anchors.horizontalCenter: parent.horizontalCenter
visible: !materialBrowserModel.hasQuick3DImport
}
ScrollView {
id: scrollView
width: root.width
height: root.height - searchBox.height
clip: true
Grid {
id: grid
width: scrollView.width
leftPadding: 5
rightPadding: 5
bottomPadding: 5
columns: root.width / root.cellWidth
Repeater {
id: gridRepeater
model: materialBrowserModel
delegate: MaterialItem {
width: root.cellWidth
height: root.cellHeight
onShowContextMenu: {
if (searchBox.isEmpty()) {
root.currentMaterial = model
contextMenu.popup()
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,134 @@
/****************************************************************************
**
** 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.
**
****************************************************************************/
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuickDesignerTheme 1.0
import HelperWidgets 2.0
import StudioTheme 1.0 as StudioTheme
Rectangle {
id: root
signal showContextMenu()
function refreshPreview()
{
img.source = ""
img.source = "image://materialBrowser/" + materialInternalId
}
function startRename()
{
matName.readOnly = false
matName.selectAll()
matName.forceActiveFocus()
nameMouseArea.enabled = false
}
function commitRename()
{
if (matName.readOnly)
return;
matName.readOnly = true
nameMouseArea.enabled = true
materialBrowserModel.renameMaterial(index, matName.text);
}
border.width: materialBrowserModel.selectedIndex === index ? 1 : 0
border.color: materialBrowserModel.selectedIndex === index
? StudioTheme.Values.themeControlOutlineInteraction
: "transparent"
color: "transparent"
visible: materialVisible
MouseArea {
id: mouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse) => {
materialBrowserModel.selectMaterial(index)
if (mouse.button === Qt.RightButton)
root.showContextMenu()
}
onDoubleClicked: materialBrowserModel.openMaterialEditor();
}
Column {
anchors.fill: parent
spacing: 1
Item { width: 1; height: 5 } // spacer
Image {
id: img
width: root.width - 10
height: img.width
anchors.horizontalCenter: parent.horizontalCenter
source: "image://materialBrowser/" + materialInternalId
cache: false
}
TextInput {
id: matName
text: materialName
width: img.width
clip: true
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: TextInput.AlignHCenter
font.pixelSize: StudioTheme.Values.myFontSize
readOnly: true
selectByMouse: !matName.readOnly
color: StudioTheme.Values.themeTextColor
selectionColor: StudioTheme.Values.themeTextSelectionColor
selectedTextColor: StudioTheme.Values.themeTextSelectedTextColor
// allow only alphanumeric characters, underscores, no space at start, and 1 space between words
validator: RegExpValidator { regExp: /^(\w+\s)*\w+$/ }
onEditingFinished: root.commitRename()
MouseArea {
id: nameMouseArea
anchors.fill: parent
onClicked: materialBrowserModel.selectMaterial(index)
onDoubleClicked: root.startRename()
}
}
}
}

View File

@@ -0,0 +1,62 @@
/****************************************************************************
**
** 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.
**
****************************************************************************/
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuickDesignerTheme 1.0
import HelperWidgets 2.0
import StudioTheme 1.0 as StudioTheme
PropertyEditorPane {
id: root
signal toolBarAction(int action)
Column {
id: col
MaterialEditorToolBar {
width: root.width
onToolBarAction: (action) => root.toolBarAction(action)
}
Item {
width: root.width - 2 * col.padding
height: 150
Text {
text: hasQuick3DImport ? qsTr("No materials yet.\nClick 'Add new material' above to start.")
: qsTr("Add QtQuick3D module using the Components view to enable the Material Editor.")
color: StudioTheme.Values.themeTextColor
font.pixelSize: StudioTheme.Values.baseFontSize
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
width: root.width
anchors.centerIn: parent
}
}
}
}

View File

@@ -0,0 +1,55 @@
/****************************************************************************
**
** 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.
**
****************************************************************************/
import QtQuick 2.15
import QtQuickDesignerTheme 1.0
import HelperWidgets 2.0
PropertyEditorPane {
id: itemPane
signal toolBarAction(int action)
// invoked from C++ to refresh material preview image
function refreshPreview()
{
topSection.refreshPreview()
}
MaterialEditorTopSection {
id: topSection
onToolBarAction: (action) => itemPane.toolBarAction(action)
}
Item { width: 1; height: 10 }
Loader {
id: specificsOne
anchors.left: parent.left
anchors.right: parent.right
source: specificsUrl
}
}

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.
**
****************************************************************************/
import QtQuick 2.15
import QtQuickDesignerTheme 1.0
import HelperWidgets 2.0
import StudioTheme 1.0 as StudioTheme
import ToolBarAction 1.0
Rectangle {
id: root
color: StudioTheme.Values.themeSectionHeadBackground
width: row.width
height: 40
signal toolBarAction(int action)
Row {
id: row
anchors.verticalCenter: parent.verticalCenter
leftPadding: 6
IconButton {
icon: StudioTheme.Constants.applyMaterialToSelected
normalColor: "transparent"
iconSize: StudioTheme.Values.bigIconFontSize
buttonSize: root.height
enabled: hasMaterial && hasQuick3DImport
onClicked: root.toolBarAction(ToolBarAction.ApplyToSelected)
tooltip: qsTr("Apply material to selected model.")
}
IconButton {
icon: StudioTheme.Constants.newMaterial
normalColor: "transparent"
iconSize: StudioTheme.Values.bigIconFontSize
buttonSize: root.height
enabled: hasQuick3DImport
onClicked: root.toolBarAction(ToolBarAction.AddNewMaterial)
tooltip: qsTr("Add a new material.")
}
IconButton {
icon: StudioTheme.Constants.deleteMaterial
normalColor: "transparent"
iconSize: StudioTheme.Values.bigIconFontSize
buttonSize: root.height
enabled: hasMaterial && hasQuick3DImport
onClicked: root.toolBarAction(ToolBarAction.DeleteCurrentMaterial)
tooltip: qsTr("Delete current material.")
}
IconButton {
icon: StudioTheme.Constants.openMaterialBrowser
normalColor: "transparent"
iconSize: StudioTheme.Values.bigIconFontSize
buttonSize: root.height
enabled: hasMaterial && hasQuick3DImport
onClicked: root.toolBarAction(ToolBarAction.OpenMaterialBrowser)
tooltip: qsTr("Open material browser.")
}
}
}

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.
**
****************************************************************************/
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuickDesignerTheme 1.0
import QtQuick.Templates 2.15 as T
import HelperWidgets 2.0
import StudioTheme 1.0 as StudioTheme
Column {
id: root
signal toolBarAction(int action)
function refreshPreview()
{
materialPreview.source = ""
materialPreview.source = "image://materialEditor/preview"
}
anchors.left: parent.left
anchors.right: parent.right
MaterialEditorToolBar {
width: root.width
onToolBarAction: (action) => root.toolBarAction(action)
}
Item { width: 1; height: 10 } // spacer
Rectangle {
width: 152
height: 152
color: "#000000"
anchors.horizontalCenter: parent.horizontalCenter
Image {
id: materialPreview
width: 150
height: 150
anchors.centerIn: parent
source: "image://materialEditor/preview"
cache: false
}
}
Section {
// Section with hidden header is used so properties are aligned with the other sections' properties
hideHeader: true
width: parent.width
SectionLayout {
PropertyLabel { text: qsTr("Name") }
SecondColumnLayout {
Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth }
LineEdit {
implicitWidth: StudioTheme.Values.singleControlColumnWidth
width: StudioTheme.Values.singleControlColumnWidth
backendValue: backendValues.objectName
placeholderText: qsTr("Material name")
text: backendValues.id.value
showTranslateCheckBox: false
showExtendedFunctionButton: false
// allow only alphanumeric characters, underscores, no space at start, and 1 space between words
validator: RegExpValidator { regExp: /^(\w+\s)*\w+$/ }
}
ExpandingSpacer {}
}
PropertyLabel { text: qsTr("Type") }
SecondColumnLayout {
Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth }
ComboBox {
currentIndex: {
if (backendValues.__classNamePrivateInternal.value === "CustomMaterial")
return 2
if (backendValues.__classNamePrivateInternal.value === "PrincipledMaterial")
return 1
return 0
}
model: ["DefaultMaterial", "PrincipledMaterial", "CustomMaterial"]
showExtendedFunctionButton: false
implicitWidth: StudioTheme.Values.singleControlColumnWidth
onActivated: changeTypeName(currentValue)
}
}
}
}
}

View File

@@ -74,6 +74,29 @@ StudioControls.ComboBox {
property alias colorLogic: colorLogic property alias colorLogic: colorLogic
DropArea {
id: dropArea
anchors.fill: parent
property string assetPath: ""
onEntered: (drag) => {
dropArea.assetPath = drag.getDataAsString(drag.keys[0]).split(",")[0]
drag.accepted = comboBox.backendValue !== undefined && comboBox.backendValue.isSupportedDrop(dropArea.assetPath)
comboBox.hasActiveDrag = drag.accepted
}
onExited: comboBox.hasActiveDrag = false
onDropped: {
comboBox.backendValue.commitDrop(dropArea.assetPath)
comboBox.hasActiveDrag = false
}
}
ExtendedFunctionLogic { ExtendedFunctionLogic {
id: extFuncLogic id: extFuncLogic
backendValue: comboBox.backendValue backendValue: comboBox.backendValue

View File

@@ -26,54 +26,58 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuickDesignerTheme 1.0 import QtQuickDesignerTheme 1.0
import HelperWidgets 2.0 as HelperWidgets
import StudioControls 1.0 as StudioControls
import StudioTheme 1.0 as StudioTheme import StudioTheme 1.0 as StudioTheme
Rectangle { Rectangle {
id: root id: root
property string tooltip: ""
signal clicked() signal clicked()
implicitWidth: 29 property alias icon: icon.text
implicitHeight: 29 property alias enabled: mouseArea.enabled
color: mouseArea.containsMouse && enabled property alias tooltip: toolTip.text
? StudioTheme.Values.themeControlBackgroundHover property alias iconSize: icon.font.pixelSize
: StudioTheme.Values.themeControlBackground
property int buttonSize: StudioTheme.Values.height
property color normalColor: StudioTheme.Values.themeControlBackground
property color hoverColor: StudioTheme.Values.themeControlBackgroundHover
property color pressColor: StudioTheme.Values.themeControlBackgroundInteraction
width: buttonSize
height: buttonSize
color: mouseArea.pressed ? pressColor
: mouseArea.containsMouse ? hoverColor
: normalColor
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: StudioTheme.Values.hoverDuration duration: 300
easing.type: StudioTheme.Values.hoverEasing easing.type: Easing.OutQuad
} }
} }
Label { // + sign Text {
text: StudioTheme.Constants.plus id: icon
color: root.enabled ? StudioTheme.Values.themeTextColor : StudioTheme.Values.themeTextColorDisabled
font.family: StudioTheme.Constants.iconFont.family font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: StudioTheme.Values.myIconFontSize font.pixelSize: StudioTheme.Values.baseIconFontSize
verticalAlignment: Text.AlignVCenter anchors.centerIn: root
horizontalAlignment: Text.AlignHCenter
anchors.centerIn: parent
color: root.enabled ? StudioTheme.Values.themeIconColor
: StudioTheme.Values.themeIconColorDisabled
scale: mouseArea.containsMouse ? 1.4 : 1
Behavior on scale {
NumberAnimation {
duration: 300
easing.type: Easing.OutExpo
}
}
} }
HelperWidgets.ToolTipArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onClicked: root.clicked() onClicked: root.clicked()
tooltip: root.tooltip }
ToolTip {
id: toolTip
visible: mouseArea.containsMouse
delay: 1000
} }
} }

View File

@@ -26,8 +26,6 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuickDesignerTheme 1.0 import QtQuickDesignerTheme 1.0
import HelperWidgets 2.0 as HelperWidgets
import StudioControls 1.0 as StudioControls
import StudioTheme 1.0 as StudioTheme import StudioTheme 1.0 as StudioTheme
Item { Item {
@@ -87,7 +85,7 @@ Item {
Label { Label {
text: StudioTheme.Constants.search text: StudioTheme.Constants.search
font.family: StudioTheme.Constants.iconFont.family font.family: StudioTheme.Constants.iconFont.family
font.pixelSize: 16 font.pixelSize: StudioTheme.Values.myIconFontSize
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 7 anchors.leftMargin: 7
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter

View File

@@ -37,6 +37,7 @@ GradientPropertySpinBox 2.0 GradientPropertySpinBox.qml
HorizontalScrollBar 2.0 HorizontalScrollBar.qml HorizontalScrollBar 2.0 HorizontalScrollBar.qml
HueSlider 2.0 HueSlider.qml HueSlider 2.0 HueSlider.qml
IconIndicator 2.0 IconIndicator.qml IconIndicator 2.0 IconIndicator.qml
IconButton 2.0 IconButton.qml
IconLabel 2.0 IconLabel.qml IconLabel 2.0 IconLabel.qml
ImagePreviewTooltipArea 2.0 ImagePreviewTooltipArea.qml ImagePreviewTooltipArea 2.0 ImagePreviewTooltipArea.qml
ImageSection 2.0 ImageSection.qml ImageSection 2.0 ImageSection.qml
@@ -59,6 +60,7 @@ RoundedPanel 2.0 RoundedPanel.qml
ScrollView 2.0 ScrollView.qml ScrollView 2.0 ScrollView.qml
SecondColumnLayout 2.0 SecondColumnLayout.qml SecondColumnLayout 2.0 SecondColumnLayout.qml
Section 2.0 Section.qml Section 2.0 Section.qml
SearchBox 2.0 SearchBox.qml
SectionLayout 2.0 SectionLayout.qml SectionLayout 2.0 SectionLayout.qml
Spacer 2.0 Spacer.qml Spacer 2.0 Spacer.qml
SpinBox 2.0 SpinBox.qml SpinBox 2.0 SpinBox.qml

View File

@@ -39,6 +39,7 @@ T.ComboBox {
&& myComboBox.enabled && myComboBox.enabled
property bool edit: myComboBox.activeFocus && myComboBox.editable property bool edit: myComboBox.activeFocus && myComboBox.editable
property bool open: comboBoxPopup.opened property bool open: comboBoxPopup.opened
property bool hasActiveDrag: false
property bool dirty: false // user modification flag property bool dirty: false // user modification flag
@@ -251,7 +252,8 @@ T.ComboBox {
PropertyChanges { PropertyChanges {
target: comboBoxBackground target: comboBoxBackground
color: StudioTheme.Values.themeControlBackground color: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeControlOutline border.color: hasActiveDrag ? StudioTheme.Values.themeInteraction
: StudioTheme.Values.themeControlOutline
} }
}, },
// This state is intended for ComboBoxes which aren't editable, but have focus e.g. via // This state is intended for ComboBoxes which aren't editable, but have focus e.g. via

View File

@@ -64,125 +64,130 @@ QtObject {
readonly property string animatedProperty: "\u003B" readonly property string animatedProperty: "\u003B"
readonly property string annotationBubble: "\u003C" readonly property string annotationBubble: "\u003C"
readonly property string annotationDecal: "\u003D" readonly property string annotationDecal: "\u003D"
readonly property string assign: "\u003E" readonly property string applyMaterialToSelected: "\u003E"
readonly property string bevelAll: "\u003F" readonly property string assign: "\u003F"
readonly property string bevelCorner: "\u0040" readonly property string bevelAll: "\u0040"
readonly property string centerHorizontal: "\u0041" readonly property string bevelCorner: "\u0041"
readonly property string centerVertical: "\u0042" readonly property string centerHorizontal: "\u0042"
readonly property string closeCross: "\u0043" readonly property string centerVertical: "\u0043"
readonly property string colorPopupClose: "\u0044" readonly property string closeCross: "\u0044"
readonly property string columnsAndRows: "\u0045" readonly property string colorPopupClose: "\u0045"
readonly property string copyStyle: "\u0046" readonly property string columnsAndRows: "\u0046"
readonly property string cornerA: "\u0047" readonly property string copyStyle: "\u0047"
readonly property string cornerB: "\u0048" readonly property string cornerA: "\u0048"
readonly property string cornersAll: "\u0049" readonly property string cornerB: "\u0049"
readonly property string curveDesigner: "\u004A" readonly property string cornersAll: "\u004A"
readonly property string curveEditor: "\u004B" readonly property string curveDesigner: "\u004B"
readonly property string decisionNode: "\u004C" readonly property string curveEditor: "\u004C"
readonly property string deleteColumn: "\u004D" readonly property string customMaterialEditor: "\u004D"
readonly property string deleteRow: "\u004E" readonly property string decisionNode: "\u004E"
readonly property string deleteTable: "\u004F" readonly property string deleteColumn: "\u004F"
readonly property string detach: "\u0050" readonly property string deleteMaterial: "\u0050"
readonly property string distributeBottom: "\u0051" readonly property string deleteRow: "\u0051"
readonly property string distributeCenterHorizontal: "\u0052" readonly property string deleteTable: "\u0052"
readonly property string distributeCenterVertical: "\u0053" readonly property string detach: "\u0053"
readonly property string distributeLeft: "\u0054" readonly property string distributeBottom: "\u0054"
readonly property string distributeOriginBottomRight: "\u0055" readonly property string distributeCenterHorizontal: "\u0055"
readonly property string distributeOriginCenter: "\u0056" readonly property string distributeCenterVertical: "\u0056"
readonly property string distributeOriginNone: "\u0057" readonly property string distributeLeft: "\u0057"
readonly property string distributeOriginTopLeft: "\u0058" readonly property string distributeOriginBottomRight: "\u0058"
readonly property string distributeRight: "\u0059" readonly property string distributeOriginCenter: "\u0059"
readonly property string distributeSpacingHorizontal: "\u005A" readonly property string distributeOriginNone: "\u005A"
readonly property string distributeSpacingVertical: "\u005B" readonly property string distributeOriginTopLeft: "\u005B"
readonly property string distributeTop: "\u005C" readonly property string distributeRight: "\u005C"
readonly property string download: "\u005D" readonly property string distributeSpacingHorizontal: "\u005D"
readonly property string downloadUnavailable: "\u005E" readonly property string distributeSpacingVertical: "\u005E"
readonly property string downloadUpdate: "\u005F" readonly property string distributeTop: "\u005F"
readonly property string downloaded: "\u0060" readonly property string download: "\u0060"
readonly property string edit: "\u0061" readonly property string downloadUnavailable: "\u0061"
readonly property string eyeDropper: "\u0062" readonly property string downloadUpdate: "\u0062"
readonly property string favorite: "\u0063" readonly property string downloaded: "\u0063"
readonly property string flowAction: "\u0064" readonly property string edit: "\u0064"
readonly property string flowTransition: "\u0065" readonly property string eyeDropper: "\u0065"
readonly property string fontStyleBold: "\u0066" readonly property string favorite: "\u0066"
readonly property string fontStyleItalic: "\u0067" readonly property string flowAction: "\u0067"
readonly property string fontStyleStrikethrough: "\u0068" readonly property string flowTransition: "\u0068"
readonly property string fontStyleUnderline: "\u0069" readonly property string fontStyleBold: "\u0069"
readonly property string gradient: "\u006A" readonly property string fontStyleItalic: "\u006A"
readonly property string gridView: "\u006B" readonly property string fontStyleStrikethrough: "\u006B"
readonly property string idAliasOff: "\u006C" readonly property string fontStyleUnderline: "\u006C"
readonly property string idAliasOn: "\u006D" readonly property string gradient: "\u006D"
readonly property string infinity: "\u006E" readonly property string gridView: "\u006E"
readonly property string keyframe: "\u006F" readonly property string idAliasOff: "\u006F"
readonly property string linkTriangle: "\u0070" readonly property string idAliasOn: "\u0070"
readonly property string linked: "\u0071" readonly property string infinity: "\u0071"
readonly property string listView: "\u0072" readonly property string keyframe: "\u0072"
readonly property string lockOff: "\u0073" readonly property string linkTriangle: "\u0073"
readonly property string lockOn: "\u0074" readonly property string linked: "\u0074"
readonly property string mergeCells: "\u0075" readonly property string listView: "\u0075"
readonly property string minus: "\u0076" readonly property string lockOff: "\u0076"
readonly property string mirror: "\u0077" readonly property string lockOn: "\u0077"
readonly property string orientation: "\u0078" readonly property string mergeCells: "\u0078"
readonly property string paddingEdge: "\u0079" readonly property string minus: "\u0079"
readonly property string paddingFrame: "\u007A" readonly property string mirror: "\u007A"
readonly property string pasteStyle: "\u007B" readonly property string newMaterial: "\u007B"
readonly property string pause: "\u007C" readonly property string openMaterialBrowser: "\u007C"
readonly property string pin: "\u007D" readonly property string orientation: "\u007D"
readonly property string play: "\u007E" readonly property string paddingEdge: "\u007E"
readonly property string plus: "\u007F" readonly property string paddingFrame: "\u007F"
readonly property string promote: "\u0080" readonly property string pasteStyle: "\u0080"
readonly property string readOnly: "\u0081" readonly property string pause: "\u0081"
readonly property string redo: "\u0082" readonly property string pin: "\u0082"
readonly property string rotationFill: "\u0083" readonly property string play: "\u0083"
readonly property string rotationOutline: "\u0084" readonly property string plus: "\u0084"
readonly property string search: "\u0085" readonly property string promote: "\u0085"
readonly property string sectionToggle: "\u0086" readonly property string readOnly: "\u0086"
readonly property string splitColumns: "\u0087" readonly property string redo: "\u0087"
readonly property string splitRows: "\u0088" readonly property string rotationFill: "\u0088"
readonly property string startNode: "\u0089" readonly property string rotationOutline: "\u0089"
readonly property string testIcon: "\u008A" readonly property string search: "\u008A"
readonly property string textAlignBottom: "\u008B" readonly property string sectionToggle: "\u008B"
readonly property string textAlignCenter: "\u008C" readonly property string splitColumns: "\u008C"
readonly property string textAlignJustified: "\u008D" readonly property string splitRows: "\u008D"
readonly property string textAlignLeft: "\u008E" readonly property string startNode: "\u008E"
readonly property string textAlignMiddle: "\u008F" readonly property string testIcon: "\u008F"
readonly property string textAlignRight: "\u0090" readonly property string textAlignBottom: "\u0090"
readonly property string textAlignTop: "\u0091" readonly property string textAlignCenter: "\u0091"
readonly property string textBulletList: "\u0092" readonly property string textAlignJustified: "\u0092"
readonly property string textFullJustification: "\u0093" readonly property string textAlignLeft: "\u0093"
readonly property string textNumberedList: "\u0094" readonly property string textAlignMiddle: "\u0094"
readonly property string tickIcon: "\u0095" readonly property string textAlignRight: "\u0095"
readonly property string translationCreateFiles: "\u0096" readonly property string textAlignTop: "\u0096"
readonly property string translationCreateReport: "\u0097" readonly property string textBulletList: "\u0097"
readonly property string translationExport: "\u0098" readonly property string textFullJustification: "\u0098"
readonly property string translationImport: "\u0099" readonly property string textNumberedList: "\u0099"
readonly property string translationSelectLanguages: "\u009A" readonly property string tickIcon: "\u009A"
readonly property string translationTest: "\u009B" readonly property string translationCreateFiles: "\u009B"
readonly property string transparent: "\u009D" readonly property string translationCreateReport: "\u009D"
readonly property string triState: "\u009E" readonly property string translationExport: "\u009E"
readonly property string triangleArcA: "\u009F" readonly property string translationImport: "\u009F"
readonly property string triangleArcB: "\u00A0" readonly property string translationSelectLanguages: "\u00A0"
readonly property string triangleCornerA: "\u00A1" readonly property string translationTest: "\u00A1"
readonly property string triangleCornerB: "\u00A2" readonly property string transparent: "\u00A2"
readonly property string unLinked: "\u00A3" readonly property string triState: "\u00A3"
readonly property string undo: "\u00A4" readonly property string triangleArcA: "\u00A4"
readonly property string unpin: "\u00A5" readonly property string triangleArcB: "\u00A5"
readonly property string upDownIcon: "\u00A6" readonly property string triangleCornerA: "\u00A6"
readonly property string upDownSquare2: "\u00A7" readonly property string triangleCornerB: "\u00A7"
readonly property string visibilityOff: "\u00A8" readonly property string unLinked: "\u00A8"
readonly property string visibilityOn: "\u00A9" readonly property string undo: "\u00A9"
readonly property string wildcard: "\u00AA" readonly property string unpin: "\u00AA"
readonly property string wizardsAutomotive: "\u00AB" readonly property string upDownIcon: "\u00AB"
readonly property string wizardsDesktop: "\u00AC" readonly property string upDownSquare2: "\u00AC"
readonly property string wizardsGeneric: "\u00AE" readonly property string visibilityOff: "\u00AE"
readonly property string wizardsMcuEmpty: "\u00AF" readonly property string visibilityOn: "\u00AF"
readonly property string wizardsMcuGraph: "\u00B0" readonly property string wildcard: "\u00B0"
readonly property string wizardsMobile: "\u00B1" readonly property string wizardsAutomotive: "\u00B1"
readonly property string wizardsUnknown: "\u00B2" readonly property string wizardsDesktop: "\u00B2"
readonly property string zoomAll: "\u00B3" readonly property string wizardsGeneric: "\u00B3"
readonly property string zoomIn: "\u00B4" readonly property string wizardsMcuEmpty: "\u00B4"
readonly property string zoomOut: "\u00B5" readonly property string wizardsMcuGraph: "\u00B5"
readonly property string zoomSelection: "\u00B6" readonly property string wizardsMobile: "\u00B6"
readonly property string wizardsUnknown: "\u00B7"
readonly property string zoomAll: "\u00B8"
readonly property string zoomIn: "\u00B9"
readonly property string zoomOut: "\u00BA"
readonly property string zoomSelection: "\u00BB"
readonly property font iconFont: Qt.font({ readonly property font iconFont: Qt.font({
"family": controlIcons.name, "family": controlIcons.name,

View File

@@ -32,13 +32,21 @@ QtObject {
property real baseHeight: 29 property real baseHeight: 29
property real baseFont: 12 property real baseFont: 12
property real mediumFont: 14
property real bigFont: 16
property real baseIconFont: 12 property real baseIconFont: 12
property real bigIconFont: 26
property real scaleFactor: 1.0 property real scaleFactor: 1.0
property real height: Math.round(values.baseHeight * values.scaleFactor) property real height: Math.round(values.baseHeight * values.scaleFactor)
property real myFontSize: Math.round(values.baseFont * values.scaleFactor) property real baseFontSize: Math.round(values.baseFont * values.scaleFactor)
property real myIconFontSize: Math.round(values.baseIconFont * values.scaleFactor) property real myFontSize: values.baseFontSize // TODO: rename all refs to myFontSize -> baseFontSize then remove myFontSize
property real mediumFontSize: Math.round(values.mediumFont * values.scaleFactor)
property real bigFontSize: Math.round(values.bigFont * values.scaleFactor)
property real baseIconFontSize: Math.round(values.baseIconFont * values.scaleFactor)
property real myIconFontSize: values.baseIconFontSize; // TODO: rename all refs to myIconFontSize -> baseIconFontSize then remove myIconFontSize
property real bigIconFontSize: Math.round(values.bigIconFont * values.scaleFactor)
property real squareComponentWidth: values.height property real squareComponentWidth: values.height
property real smallRectWidth: values.height / 2 * 1.5 property real smallRectWidth: values.height / 2 * 1.5

View File

@@ -312,6 +312,23 @@ extend_qtc_plugin(QmlDesigner
quick2propertyeditorview.cpp quick2propertyeditorview.h 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 extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components SOURCES_PREFIX components
SOURCES resources/resources.qrc SOURCES resources/resources.qrc

View File

@@ -80,6 +80,7 @@ const char mergeTemplateCommandId[] = "MergeTemplate";
const char goToImplementationCommandId[] = "GoToImplementation"; const char goToImplementationCommandId[] = "GoToImplementation";
const char addSignalHandlerCommandId[] = "AddSignalHandler"; const char addSignalHandlerCommandId[] = "AddSignalHandler";
const char moveToComponentCommandId[] = "MoveToComponent"; const char moveToComponentCommandId[] = "MoveToComponent";
const char editMaterialCommandId[] = "EditMaterial";
const char addItemToStackedContainerCommandId[] = "AddItemToStackedContainer"; const char addItemToStackedContainerCommandId[] = "AddItemToStackedContainer";
const char addTabBarToStackedContainerCommandId[] = "AddTabBarToStackedContainer"; const char addTabBarToStackedContainerCommandId[] = "AddTabBarToStackedContainer";
const char increaseIndexOfStackedContainerCommandId[] = "IncreaseIndexOfStackedContainer"; 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 goToImplementationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Go to Implementation");
const char addSignalHandlerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Add New Signal Handler"); 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 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 editAnnotationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Annotation");
const char openSignalDialogDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Signal Dialog"); const char openSignalDialogDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Signal Dialog");

View File

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

View File

@@ -27,6 +27,7 @@
#include "modelnodeoperations.h" #include "modelnodeoperations.h"
#include "abstractaction.h" #include "abstractaction.h"
#include "bindingproperty.h"
#include "abstractactiongroup.h" #include "abstractactiongroup.h"
#include "qmlitemnode.h" #include "qmlitemnode.h"
#include <qmldesignerplugin.h> #include <qmldesignerplugin.h>
@@ -63,6 +64,24 @@ inline bool singleSelection(const SelectionContext &selectionState)
return selectionState.singleNodeIsSelected(); 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) inline bool selectionEnabled(const SelectionContext &selectionState)
{ {
return selectionState.showSelectionTools(); return selectionState.showSelectionTools();

View File

@@ -24,6 +24,7 @@
****************************************************************************/ ****************************************************************************/
#include "modelnodeoperations.h" #include "modelnodeoperations.h"
#include "designmodewidget.h"
#include "modelnodecontextmenu_helper.h" #include "modelnodecontextmenu_helper.h"
#include "addimagesdialog.h" #include "addimagesdialog.h"
#include "layoutingridlayout.h" #include "layoutingridlayout.h"
@@ -789,6 +790,37 @@ void addNewSignalHandler(const SelectionContext &selectionState)
addSignalHandlerOrGotoImplementation(selectionState, true); 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) void addItemToStackedContainer(const SelectionContext &selectionContext)
{ {
AbstractView *view = selectionContext.view(); AbstractView *view = selectionContext.view();

View File

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

View File

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

View File

@@ -30,6 +30,11 @@
#include "nodehints.h" #include "nodehints.h"
#include "qmlvisualnode.h" #include "qmlvisualnode.h"
#include <bindingproperty.h>
#include <nodemetainfo.h>
#include <nodelistproperty.h>
#include <variantproperty.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
@@ -181,13 +186,41 @@ void Edit3DCanvas::dragEnterEvent(QDragEnterEvent *e)
void Edit3DCanvas::dropEvent(QDropEvent *e) void Edit3DCanvas::dropEvent(QDropEvent *e)
{ {
Q_UNUSED(e)
auto modelNode = QmlVisualNode::createQml3DNode(m_parent->view(), m_itemLibraryEntry, m_activeScene).modelNode(); auto modelNode = QmlVisualNode::createQml3DNode(m_parent->view(), m_itemLibraryEntry, m_activeScene).modelNode();
QTC_ASSERT(modelNode.isValid(), return);
if (modelNode.isValid()) { e->accept();
e->accept(); m_parent->view()->setSelectedModelNode(modelNode);
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() void Edit3DView::checkImports()
{ {
bool has3dImport = false; edit3DWidget()->showCanvas(model()->hasImport("QtQuick3D"));
const QList<Import> imports = model()->imports();
for (const auto &import : imports) {
if (import.url() == "QtQuick3D") {
has3dImport = true;
break;
}
}
edit3DWidget()->showCanvas(has3dImport);
} }
WidgetInfo Edit3DView::widgetInfo() 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 <abstractview.h>
#include <invalididexception.h> #include <invalididexception.h>
#include <rewritingexception.h> #include <rewritingexception.h>
#include <qmldesignerconstants.h>
#include <qmlitemnode.h> #include <qmlitemnode.h>
#include <designeractionmanager.h> #include <designeractionmanager.h>
#include <import.h> #include <import.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <qmlprojectmanager/qmlproject.h> #include <qmlprojectmanager/qmlproject.h>
@@ -339,7 +339,8 @@ QList<ModelNode> NavigatorTreeModel::filteredList(const NodeListProperty &proper
if (filter) { if (filter) {
list.append(Utils::filtered(nameFilteredList, [] (const ModelNode &arg) { 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; return value;
})); }));
} else { } else {
@@ -688,13 +689,20 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in
return; return;
bool validContainer = false; 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; QmlObjectNode newQmlObjectNode;
m_view->executeInTransaction("NavigatorTreeModel::handleItemLibraryItemDrop", [&] { m_view->executeInTransaction("NavigatorTreeModel::handleItemLibraryItemDrop", [&] {
newQmlObjectNode = QmlItemNode::createQmlObjectNode(m_view, itemLibraryEntry, QPointF(), targetProperty, false); newQmlObjectNode = QmlItemNode::createQmlObjectNode(m_view, itemLibraryEntry, QPointF(), targetProperty, false);
ModelNode newModelNode = newQmlObjectNode.modelNode(); ModelNode newModelNode = newQmlObjectNode.modelNode();
if (newModelNode.isValid()) { if (newModelNode.isValid()) {
ModelNode targetNode = targetProperty.parentModelNode();
ChooseFromPropertyListDialog *dialog = ChooseFromPropertyListDialog::createIfNeeded( ChooseFromPropertyListDialog *dialog = ChooseFromPropertyListDialog::createIfNeeded(
targetNode, newModelNode, Core::ICore::dialogParent()); targetNode, newModelNode, Core::ICore::dialogParent());
if (dialog) { if (dialog) {
@@ -728,17 +736,35 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in
} }
delete dialog; 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") if (newModelNode.isSubclassOf("QtQuick3D.Material")
&& targetProperty.parentModelNode().isSubclassOf("QtQuick3D.Node") && targetNode.isSubclassOf("QtQuick3D.Model")) {
&& targetProperty.parentModelNode().isComponent()) { // parent material to material library and assign it to target model
// Inserting materials under imported components is likely a mistake, so ModelNode matLib = m_view->modelNodeForId(Constants::MATERIAL_LIB_ID);
// notify user with a helpful messagebox that suggests the correct action.
showMatToCompInfo = true; 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 (!validContainer) {
if (!showMatToCompInfo) validContainer = NodeHints::fromModelNode(targetProperty.parentModelNode()).canBeContainerFor(newModelNode);
validContainer = NodeHints::fromModelNode(targetProperty.parentModelNode()).canBeContainerFor(newModelNode);
if (!validContainer) if (!validContainer)
newQmlObjectNode.destroy(); 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 {}; 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) TypeName propertyType(const NodeAbstractProperty &property)
{ {
return property.parentModelNode().metaInfo().propertyTypeName(property.name()); return property.parentModelNode().metaInfo().propertyTypeName(property.name());

View File

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

View File

@@ -24,12 +24,15 @@
****************************************************************************/ ****************************************************************************/
#include "propertyeditorvalue.h" #include "propertyeditorvalue.h"
#include "variantproperty.h"
#include "documentmanager.h"
#include <abstractview.h> #include <abstractview.h>
#include <bindingproperty.h> #include <bindingproperty.h>
#include <designdocument.h> #include <designdocument.h>
#include <nodeproperty.h> #include <nodelistproperty.h>
#include <nodemetainfo.h> #include <nodemetainfo.h>
#include <nodeproperty.h>
#include <qmldesignerplugin.h> #include <qmldesignerplugin.h>
#include <qmlobjectnode.h> #include <qmlobjectnode.h>
#include <designermcumanager.h> #include <designermcumanager.h>
@@ -40,6 +43,7 @@
#include <QRegularExpression> #include <QRegularExpression>
#include <QUrl> #include <QUrl>
#include <QScopedPointer> #include <QScopedPointer>
#include <assetslibrarymodel.h>
//using namespace QmlDesigner; //using namespace QmlDesigner;
@@ -367,6 +371,18 @@ void PropertyEditorValue::setEnumeration(const QString &scope, const QString &na
setValueWithEmit(QVariant::fromValue(newEnumeration)); 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() void PropertyEditorValue::exportPropertyAsAlias()
{ {
emit exportPropertyAsAliasRequested(nameAsQString()); emit exportPropertyAsAliasRequested(nameAsQString());
@@ -499,6 +515,29 @@ bool PropertyEditorValue::idListReplace(int idx, const QString &value)
return true; 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 QStringList PropertyEditorValue::generateStringList(const QString &string) const
{ {
QString copy = string; QString copy = string;

View File

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

View File

@@ -125,6 +125,7 @@ public:
void clearMetaInfoCache(); void clearMetaInfoCache();
bool hasId(const QString &id) const; 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 generateNewId(const QString &prefixName, const QString &fallbackPrefix) 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); 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) static QString firstCharToLower(const QString &string)
{ {
QString resultString = string; QString resultString = string;

View File

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

View File

@@ -583,9 +583,9 @@ CrumbleBar *DesignModeWidget::crumbleBar() const
return m_crumbleBar; 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) if (dockWidget)
dockWidget->toggleView(true); dockWidget->toggleView(true);
} }

View File

@@ -79,14 +79,14 @@ public:
void enableWidgets(); void enableWidgets();
void disableWidgets(); void disableWidgets();
CrumbleBar* crumbleBar() const; CrumbleBar *crumbleBar() const;
void showInternalTextEditor(); void showDockWidget(const QString &objectName);
void determineWorkspaceToRestoreAtStartup(); void determineWorkspaceToRestoreAtStartup();
static QWidget *createProjectExplorerWidget(QWidget *parent); static QWidget *createProjectExplorerWidget(QWidget *parent);
private: // functions private:
enum InitializeStatus { NotInitialized, Initializing, Initialized }; enum InitializeStatus { NotInitialized, Initializing, Initialized };
void toolBarOnGoBackClicked(); void toolBarOnGoBackClicked();
@@ -101,7 +101,6 @@ private: // functions
void aboutToShowWorkspaces(); void aboutToShowWorkspaces();
private: // variables
QPointer<QWidget> m_bottomSideBar; QPointer<QWidget> m_bottomSideBar;
Core::EditorToolBar *m_toolBar; Core::EditorToolBar *m_toolBar;
CrumbleBar *m_crumbleBar; 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_OPTIONS_KEY[] = "import_options";
const char QUICK_3D_ASSET_IMPORT_DATA_SOURCE_KEY[] = "source_scene"; const char QUICK_3D_ASSET_IMPORT_DATA_SOURCE_KEY[] = "source_scene";
const char DEFAULT_ASSET_IMPORT_FOLDER[] = "/asset_imports"; 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_ITEM_LIBRARY_INFO[] = "application/vnd.qtdesignstudio.itemlibraryinfo";
const char MIME_TYPE_ASSETS[] = "application/vnd.qtdesignstudio.assets"; const char MIME_TYPE_ASSETS[] = "application/vnd.qtdesignstudio.assets";

View File

@@ -488,7 +488,7 @@ Project {
"componentcore/formatoperation.h", "componentcore/formatoperation.h",
"componentcore/layoutingridlayout.cpp", "componentcore/layoutingridlayout.cpp",
"componentcore/layoutingridlayout.h", "componentcore/layoutingridlayout.h",
"componentcore/theme.cpp", "componentcore/theme.cpp",
"componentcore/theme.h", "componentcore/theme.h",
"componentcore/modelnodecontextmenu.cpp", "componentcore/modelnodecontextmenu.cpp",
"componentcore/modelnodecontextmenu.h", "componentcore/modelnodecontextmenu.h",
@@ -675,6 +675,20 @@ Project {
"itemlibrary/itemlibrarywidget.h", "itemlibrary/itemlibrarywidget.h",
"itemlibrary/itemlibraryiconimageprovider.cpp", "itemlibrary/itemlibraryiconimageprovider.cpp",
"itemlibrary/itemlibraryiconimageprovider.h", "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.cpp",
"navigator/iconcheckboxitemdelegate.h", "navigator/iconcheckboxitemdelegate.h",
"navigator/nameitemdelegate.cpp", "navigator/nameitemdelegate.cpp",