QmlDesigner: Highlight material editor properties upon asset drag

When starting an asset drag in the assets view, highlight all
supported properties in the material editor.

Change-Id: I60935756e4c1384edcc284068163d08ebe529a05
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Samuel Ghinet <samuel.ghinet@qt.io>
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
Mahmoud Badri
2022-05-30 15:31:13 +03:00
parent 0a84ef4b2c
commit 175343e24a
11 changed files with 101 additions and 53 deletions

View File

@@ -41,6 +41,8 @@ StudioControls.ComboBox {
onModelChanged: colorLogic.invalidate() onModelChanged: colorLogic.invalidate()
hasActiveDrag: comboBox.backendValue !== undefined && comboBox.backendValue.hasActiveDrag
// This is available in all editors. // This is available in all editors.
onValueTypeChanged: { onValueTypeChanged: {
@@ -83,16 +85,15 @@ StudioControls.ComboBox {
onEntered: (drag) => { onEntered: (drag) => {
dropArea.assetPath = drag.getDataAsString(drag.keys[0]).split(",")[0] dropArea.assetPath = drag.getDataAsString(drag.keys[0]).split(",")[0]
drag.accepted = comboBox.backendValue !== undefined && comboBox.backendValue.hasActiveDrag
drag.accepted = comboBox.backendValue !== undefined && comboBox.backendValue.isSupportedDrop(dropArea.assetPath) comboBox.hasActiveHoverDrag = drag.accepted
comboBox.hasActiveDrag = drag.accepted
} }
onExited: comboBox.hasActiveDrag = false onExited: comboBox.hasActiveHoverDrag = false
onDropped: { onDropped: {
comboBox.backendValue.commitDrop(dropArea.assetPath) comboBox.backendValue.commitDrop(dropArea.assetPath)
comboBox.hasActiveDrag = false comboBox.hasActiveHoverDrag = false
} }
} }

View File

@@ -39,7 +39,8 @@ 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 hasActiveDrag: false // an item that can be dropped on the combobox is being dragged
property bool hasActiveHoverDrag: false // an item that can be dropped on the combobox is being hovered on the combobox
property bool dirty: false // user modification flag property bool dirty: false // user modification flag
@@ -49,6 +50,9 @@ T.ComboBox {
property alias textInput: comboBoxInput property alias textInput: comboBoxInput
property int borderWidth: myComboBox.hasActiveHoverDrag ? StudioTheme.Values.borderHover
: StudioTheme.Values.border
signal compressedActivated(int index, int reason) signal compressedActivated(int index, int reason)
enum ActivatedReason { EditingFinished, Other } enum ActivatedReason { EditingFinished, Other }
@@ -57,7 +61,7 @@ T.ComboBox {
height: StudioTheme.Values.defaultControlHeight height: StudioTheme.Values.defaultControlHeight
leftPadding: actionIndicator.width leftPadding: actionIndicator.width
rightPadding: popupIndicator.width + StudioTheme.Values.border rightPadding: popupIndicator.width + myComboBox.borderWidth
font.pixelSize: StudioTheme.Values.myFontSize font.pixelSize: StudioTheme.Values.myFontSize
wheelEnabled: false wheelEnabled: false
@@ -87,6 +91,7 @@ T.ComboBox {
myControl: myComboBox myControl: myComboBox
text: myComboBox.editText text: myComboBox.editText
borderWidth: myComboBox.borderWidth
onEditingFinished: { onEditingFinished: {
comboBoxInput.deselect() comboBoxInput.deselect()
@@ -108,16 +113,16 @@ T.ComboBox {
myControl: myComboBox myControl: myComboBox
myPopup: myComboBox.popup myPopup: myComboBox.popup
x: comboBoxInput.x + comboBoxInput.width x: comboBoxInput.x + comboBoxInput.width
y: StudioTheme.Values.border y: myComboBox.borderWidth
width: StudioTheme.Values.checkIndicatorWidth - StudioTheme.Values.border width: StudioTheme.Values.checkIndicatorWidth - myComboBox.borderWidth
height: StudioTheme.Values.checkIndicatorHeight - (StudioTheme.Values.border * 2) height: StudioTheme.Values.checkIndicatorHeight - myComboBox.borderWidth * 2
} }
background: Rectangle { background: Rectangle {
id: comboBoxBackground id: comboBoxBackground
color: StudioTheme.Values.themeControlBackground color: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeControlOutline border.color: StudioTheme.Values.themeControlOutline
border.width: StudioTheme.Values.border border.width: myComboBox.borderWidth
x: actionIndicator.width x: actionIndicator.width
width: myComboBox.width - actionIndicator.width width: myComboBox.width - actionIndicator.width
height: myComboBox.height height: myComboBox.height
@@ -144,7 +149,7 @@ T.ComboBox {
width: comboBoxPopup.width - comboBoxPopup.leftPadding - comboBoxPopup.rightPadding width: comboBoxPopup.width - comboBoxPopup.leftPadding - comboBoxPopup.rightPadding
- (comboBoxPopupScrollBar.visible ? comboBoxPopupScrollBar.contentItem.implicitWidth - (comboBoxPopupScrollBar.visible ? comboBoxPopupScrollBar.contentItem.implicitWidth
+ 2 : 0) // TODO Magic number + 2 : 0) // TODO Magic number
height: StudioTheme.Values.height - 2 * StudioTheme.Values.border height: StudioTheme.Values.height - 2 * myComboBox.borderWidth
padding: 0 padding: 0
enabled: model.enabled === undefined ? true : model.enabled enabled: model.enabled === undefined ? true : model.enabled
@@ -198,9 +203,9 @@ T.ComboBox {
popup: T.Popup { popup: T.Popup {
id: comboBoxPopup id: comboBoxPopup
x: actionIndicator.width + StudioTheme.Values.border x: actionIndicator.width + myComboBox.borderWidth
y: myComboBox.height y: myComboBox.height
width: myComboBox.width - actionIndicator.width - (StudioTheme.Values.border * 2) width: myComboBox.width - actionIndicator.width - myComboBox.borderWidth * 2
// TODO Setting the height on the popup solved the problem with the popup of height 0, // TODO Setting the height on the popup solved the problem with the popup of height 0,
// but it has the problem that it sometimes extend over the border of the actual window // but it has the problem that it sometimes extend over the border of the actual window
// and is then cut off. // and is then cut off.
@@ -208,7 +213,7 @@ T.ComboBox {
+ comboBoxPopup.bottomPadding, + comboBoxPopup.bottomPadding,
myComboBox.Window.height - topMargin - bottomMargin, myComboBox.Window.height - topMargin - bottomMargin,
StudioTheme.Values.maxComboBoxPopupHeight) StudioTheme.Values.maxComboBoxPopupHeight)
padding: StudioTheme.Values.border padding: myComboBox.borderWidth
margins: 0 // If not defined margin will be -1 margins: 0 // If not defined margin will be -1
closePolicy: T.Popup.CloseOnPressOutside | T.Popup.CloseOnPressOutsideParent closePolicy: T.Popup.CloseOnPressOutside | T.Popup.CloseOnPressOutsideParent
| T.Popup.CloseOnEscape | T.Popup.CloseOnReleaseOutside | T.Popup.CloseOnEscape | T.Popup.CloseOnReleaseOutside
@@ -252,8 +257,9 @@ T.ComboBox {
PropertyChanges { PropertyChanges {
target: comboBoxBackground target: comboBoxBackground
color: StudioTheme.Values.themeControlBackground color: StudioTheme.Values.themeControlBackground
border.color: hasActiveDrag ? StudioTheme.Values.themeInteraction border.color: myComboBox.hasActiveDrag ? StudioTheme.Values.themeInteraction
: StudioTheme.Values.themeControlOutline : StudioTheme.Values.themeControlOutline
border.width: myComboBox.borderWidth
} }
}, },
// 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

@@ -34,6 +34,7 @@ TextInput {
property bool edit: textInput.activeFocus property bool edit: textInput.activeFocus
property bool hover: mouseArea.containsMouse && textInput.enabled property bool hover: mouseArea.containsMouse && textInput.enabled
property int borderWidth: StudioTheme.Values.border
z: 2 z: 2
font: myControl.font font: myControl.font
@@ -55,11 +56,11 @@ TextInput {
Rectangle { Rectangle {
id: textInputBackground id: textInputBackground
x: StudioTheme.Values.border x: textInput.borderWidth
y: StudioTheme.Values.border y: textInput.borderWidth
z: -1 z: -1
width: textInput.width width: textInput.width
height: StudioTheme.Values.height - (StudioTheme.Values.border * 2) height: StudioTheme.Values.height - textInput.borderWidth * 2
color: StudioTheme.Values.themeControlBackground color: StudioTheme.Values.themeControlBackground
border.width: 0 border.width: 0
} }

View File

@@ -86,6 +86,7 @@ QtObject {
property real marginTopBottom: 4 property real marginTopBottom: 4
property real border: 1 property real border: 1
property real borderHover: 3
property real maxComboBoxPopupHeight: Math.round(300 * values.scaleFactor) property real maxComboBoxPopupHeight: Math.round(300 * values.scaleFactor)
property real maxTextAreaPopupHeight: Math.round(150 * values.scaleFactor) property real maxTextAreaPopupHeight: Math.round(150 * values.scaleFactor)

View File

@@ -81,30 +81,20 @@ bool AssetsLibraryWidget::eventFilter(QObject *obj, QEvent *event)
if (obj == m_assetsWidget.data()) if (obj == m_assetsWidget.data())
QMetaObject::invokeMethod(m_assetsWidget->rootObject(), "handleViewFocusOut"); QMetaObject::invokeMethod(m_assetsWidget->rootObject(), "handleViewFocusOut");
} else if (event->type() == QMouseEvent::MouseMove) { } else if (event->type() == QMouseEvent::MouseMove) {
if (!m_assetsToDrag.isEmpty()) { if (!m_assetsToDrag.isEmpty() && !m_model.isNull()) {
QMouseEvent *me = static_cast<QMouseEvent *>(event); QMouseEvent *me = static_cast<QMouseEvent *>(event);
if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 10) { if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 10) {
auto drag = new QDrag(this);
drag->setPixmap(m_assetsIconProvider->requestPixmap(m_assetsToDrag[0], nullptr, {128, 128}));
QMimeData *mimeData = new QMimeData; QMimeData *mimeData = new QMimeData;
mimeData->setData(Constants::MIME_TYPE_ASSETS, m_assetsToDrag.join(',').toUtf8()); mimeData->setData(Constants::MIME_TYPE_ASSETS, m_assetsToDrag.join(',').toUtf8());
drag->setMimeData(mimeData); m_model->startDrag(mimeData,
drag->exec(); m_assetsIconProvider->requestPixmap(m_assetsToDrag[0], nullptr, {128, 128}));
drag->deleteLater();
m_assetsToDrag.clear(); m_assetsToDrag.clear();
} }
} }
} else if (event->type() == QMouseEvent::MouseButtonRelease) { } else if (event->type() == QMouseEvent::MouseButtonRelease) {
m_assetsToDrag.clear(); m_assetsToDrag.clear();
QWidget *view = QmlDesignerPlugin::instance()->viewManager().widget("Navigator"); if (m_model)
if (view) { m_model->endDrag();
NavigatorWidget *navView = qobject_cast<NavigatorWidget *>(view);
if (navView) {
navView->setDragType("");
navView->update();
}
}
} }
return QObject::eventFilter(obj, event); return QObject::eventFilter(obj, event);

View File

@@ -29,6 +29,7 @@
#include "materialeditorcontextobject.h" #include "materialeditorcontextobject.h"
#include "propertyeditorvalue.h" #include "propertyeditorvalue.h"
#include "materialeditortransaction.h" #include "materialeditortransaction.h"
#include "assetslibrarywidget.h"
#include <qmldesignerconstants.h> #include <qmldesignerconstants.h>
#include <qmltimeline.h> #include <qmltimeline.h>
@@ -49,6 +50,7 @@
#include <coreplugin/messagebox.h> #include <coreplugin/messagebox.h>
#include <designmodewidget.h> #include <designmodewidget.h>
#include <qmldesignerplugin.h> #include <qmldesignerplugin.h>
#include <utils/algorithm.h>
#include <utils/fileutils.h> #include <utils/fileutils.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
@@ -798,6 +800,42 @@ void MaterialEditorView::customNotification(const AbstractView *view, const QStr
} }
} }
void QmlDesigner::MaterialEditorView::highlightSupportedProperties(bool highlight)
{
DesignerPropertyMap &propMap = m_qmlBackEnd->backendValuesPropertyMap();
const QStringList propNames = propMap.keys();
for (const QString &propName : propNames) {
if (propName.endsWith("Map")) {
QObject *propEditorValObj = propMap.value(propName).value<QObject *>();
PropertyEditorValue *propEditorVal = qobject_cast<PropertyEditorValue *>(propEditorValObj);
propEditorVal->setHasActiveDrag(highlight);
}
}
}
void MaterialEditorView::dragStarted(QMimeData *mimeData)
{
if (!mimeData->hasFormat(Constants::MIME_TYPE_ASSETS))
return;
const QStringList assetPaths = QString::fromUtf8(mimeData->data(Constants::MIME_TYPE_ASSETS)).split(',');
bool isImage = Utils::anyOf(assetPaths, [] (const QString &assetPath) {
QString assetType = AssetsLibraryWidget::getAssetTypeAndData(assetPath).first;
return assetType == Constants::MIME_TYPE_ASSET_IMAGE;
});
if (!isImage) // only image assets are dnd supported
return;
highlightSupportedProperties();
}
void MaterialEditorView::dragEnded()
{
highlightSupportedProperties(false);
}
// from model to material editor // from model to material editor
void MaterialEditorView::setValue(const QmlObjectNode &qmlObjectNode, const PropertyName &name, const QVariant &value) void MaterialEditorView::setValue(const QmlObjectNode &qmlObjectNode, const PropertyName &name, const QVariant &value)
{ {

View File

@@ -73,6 +73,9 @@ public:
void customNotification(const AbstractView *view, const QString &identifier, void customNotification(const AbstractView *view, const QString &identifier,
const QList<ModelNode> &nodeList, const QList<QVariant> &data) override; const QList<ModelNode> &nodeList, const QList<QVariant> &data) override;
void dragStarted(QMimeData *mimeData) override;
void dragEnded() override;
void changeValue(const QString &name); void changeValue(const QString &name);
void changeExpression(const QString &name); void changeExpression(const QString &name);
void exportPropertyAsAlias(const QString &name); void exportPropertyAsAlias(const QString &name);
@@ -93,6 +96,7 @@ private:
static QString materialEditorResourcesPath(); static QString materialEditorResourcesPath();
void reloadQml(); void reloadQml();
void highlightSupportedProperties(bool highlight = true);
QString generateIdFromName(const QString &name); QString generateIdFromName(const QString &name);
void ensureMaterialLibraryNode(); void ensureMaterialLibraryNode();

View File

@@ -274,6 +274,19 @@ bool PropertyEditorValue::isTranslated() const
return false; return false;
} }
bool PropertyEditorValue::hasActiveDrag() const
{
return m_hasActiveDrag;
}
void PropertyEditorValue::setHasActiveDrag(bool val)
{
if (m_hasActiveDrag != val) {
m_hasActiveDrag = val;
emit hasActiveDragChanged();
}
}
static bool isAllowedSubclassType(const QString &type, const QmlDesigner::NodeMetaInfo &metaInfo) static bool isAllowedSubclassType(const QString &type, const QmlDesigner::NodeMetaInfo &metaInfo)
{ {
if (!metaInfo.isValid()) if (!metaInfo.isValid())
@@ -371,18 +384,6 @@ 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());

View File

@@ -82,6 +82,7 @@ class PropertyEditorValue : public QObject
Q_PROPERTY(bool isBound READ isBound NOTIFY isBoundChanged FINAL) Q_PROPERTY(bool isBound READ isBound NOTIFY isBoundChanged FINAL)
Q_PROPERTY(bool isValid READ isValid NOTIFY isValidChanged FINAL) Q_PROPERTY(bool isValid READ isValid NOTIFY isValidChanged FINAL)
Q_PROPERTY(bool isTranslated READ isTranslated NOTIFY expressionChanged FINAL) Q_PROPERTY(bool isTranslated READ isTranslated NOTIFY expressionChanged FINAL)
Q_PROPERTY(bool hasActiveDrag READ hasActiveDrag WRITE setHasActiveDrag NOTIFY hasActiveDragChanged FINAL)
Q_PROPERTY(bool isIdList READ isIdList NOTIFY expressionChanged FINAL) Q_PROPERTY(bool isIdList READ isIdList NOTIFY expressionChanged FINAL)
Q_PROPERTY(QStringList expressionAsList READ getExpressionAsList NOTIFY expressionChanged FINAL) Q_PROPERTY(QStringList expressionAsList READ getExpressionAsList NOTIFY expressionChanged FINAL)
@@ -117,6 +118,9 @@ public:
bool isTranslated() const; bool isTranslated() const;
bool hasActiveDrag() const;
void setHasActiveDrag(bool val);
bool isAvailable() const; bool isAvailable() const;
QmlDesigner::PropertyName name() const; QmlDesigner::PropertyName name() const;
@@ -148,7 +152,6 @@ public:
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&);
@@ -164,6 +167,7 @@ signals:
void isBoundChanged(); void isBoundChanged();
void isValidChanged(); void isValidChanged();
void isExplicitChanged(); void isExplicitChanged();
void hasActiveDragChanged();
private: private:
QStringList generateStringList(const QString &string) const; QStringList generateStringList(const QString &string) const;
@@ -176,6 +180,7 @@ private:
bool m_isInSubState; bool m_isInSubState;
bool m_isInModel; bool m_isInModel;
bool m_isBound; bool m_isBound;
bool m_hasActiveDrag = false;
bool m_isValid; // if the property value belongs to a non-existing complexProperty it is invalid bool m_isValid; // if the property value belongs to a non-existing complexProperty it is invalid
PropertyEditorNodeWrapper *m_complexNode; PropertyEditorNodeWrapper *m_complexNode;
}; };

View File

@@ -35,6 +35,7 @@
#include <import.h> #include <import.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QPixmap;
class QUrl; class QUrl;
QT_END_NAMESPACE QT_END_NAMESPACE
@@ -44,7 +45,7 @@ namespace Internal {
class ModelPrivate; class ModelPrivate;
class WriteLocker; class WriteLocker;
class NodeMetaInfoPrivate; class NodeMetaInfoPrivate;
} //Internal } // namespace Internal
class AnchorLine; class AnchorLine;
class ModelNode; class ModelNode;
@@ -130,7 +131,7 @@ public:
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;
void startDrag(QMimeData *mimeData, const QString iconPath = {}); void startDrag(QMimeData *mimeData, const QPixmap &icon);
void endDrag(); void endDrag();
protected: protected:
@@ -140,4 +141,4 @@ private:
Internal::ModelPrivate *d; Internal::ModelPrivate *d;
}; };
} } // namespace QmlDesigner

View File

@@ -1510,12 +1510,12 @@ QString Model::generateNewId(const QString &prefixName, const QString &fallbackP
return newId; return newId;
} }
void Model::startDrag(QMimeData *mimeData, const QString iconPath) void Model::startDrag(QMimeData *mimeData, const QPixmap &icon)
{ {
d->notifyDragStarted(mimeData); d->notifyDragStarted(mimeData);
auto drag = new QDrag(this); auto drag = new QDrag(this);
drag->setPixmap(iconPath); drag->setPixmap(icon);
drag->setMimeData(mimeData); drag->setMimeData(mimeData);
drag->exec(); drag->exec();
drag->deleteLater(); drag->deleteLater();