QmlDesigner: Implement effect maker nodes popup and load data in it

Change-Id: I95625f2eaf8aac71679b2f816dd20a9167849830
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
Mahmoud Badri
2023-08-11 12:53:28 +03:00
parent 63dd551b06
commit 6e2c62776b
14 changed files with 296 additions and 66 deletions

View File

@@ -12,9 +12,6 @@ import EffectMakerBackend
Item {
id: root
property var effectMakerModel: EffectMakerBackend.effectMakerModel
property var rootView: EffectMakerBackend.rootView
Column {
id: col
anchors.fill: parent
@@ -28,7 +25,7 @@ Item {
color: StudioTheme.Values.themeToolbarBackground
Row {
// TODO: Filter row
// TODO
}
}
@@ -62,6 +59,7 @@ Item {
effectNodesWindow.x = a.x + b.x + effectNodesComboBox.width - effectNodesWindow.width
effectNodesWindow.y = a.y + b.y + effectNodesComboBox.height - 1
effectNodesWindow.show()
effectNodesWindow.requestActivate()
}
@@ -74,7 +72,7 @@ Item {
Window {
id: effectNodesWindow
width: 600
width: row.width
height: Math.min(400, Screen.height - y - 40) // TODO: window sizing will be refined
flags: Qt.Popup | Qt.FramelessWindowHint
@@ -85,9 +83,84 @@ Item {
Rectangle {
anchors.fill: parent
color: StudioTheme.Values.themePopupBackground
color: StudioTheme.Values.themePanelBackground
border.color: StudioTheme.Values.themeInteraction
border.width: 1
Row {
id: row
onWidthChanged: {
// Needed to update on first window showing, as row.width only gets
// correct value after the window is shown, so first showing is off
var a = root.mapToGlobal(0, 0)
var b = effectNodesComboBox.mapToItem(root, 0, 0)
effectNodesWindow.x = a.x + b.x + effectNodesComboBox.width - row.width
}
padding: 10
spacing: 10
Repeater {
model: EffectMakerBackend.effectMakerNodesModel
Column {
spacing: 10
Text {
text: categoryName
color: StudioTheme.Values.themeTextColor
font.pointSize: StudioTheme.Values.baseFontSize
}
Item { width: 1; height: 10 } // spacer
Repeater {
model: categoryNodes
Rectangle {
width: 180
height: 30
color: mouseArea.containsMouse ? StudioTheme.Values.themeControlBackgroundInteraction
: "transparent"
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
}
Row {
spacing: 5
Image {
id: nodeIcon
width: 30
height: 30
Rectangle { // TODO: placeholder until setting image source
anchors.fill: parent
color: "gray"
}
}
Text {
text: modelData.nodeName
color: StudioTheme.Values.themeTextColor
font.pointSize: StudioTheme.Values.baseFontSize
anchors.verticalCenter: nodeIcon.verticalCenter
}
}
}
}
}
}
}
}
}
}
@@ -123,7 +196,7 @@ Item {
Repeater {
id: categories
width: root.width
model: effectMakerModel
model: EffectMakerBackend.effectMakerModel
delegate: HelperWidgets.Section {
id: effectsSection

View File

@@ -712,8 +712,9 @@ extend_qtc_plugin(QmlDesigner
effectmakerwidget.cpp effectmakerwidget.h
effectmakerview.cpp effectmakerview.h
effectmakermodel.cpp effectmakermodel.h
effectmakernodesmodel.cpp effectmakernodesmodel.h
effectnode.cpp effectnode.h
effectscategory.cpp effectscategory.h
effectnodescategory.cpp effectnodescategory.h
)
extend_qtc_plugin(QmlDesigner

View File

@@ -40,13 +40,13 @@ QVariant EffectMakerModel::data(const QModelIndex &index, int role) const
if (index.row() < 0 || index.row() >= m_categories.count())
return {};
const EffectsCategory *category = m_categories[index.row()];
const EffectNodesCategory *category = m_categories.at(index.row());
if (role == CategoryRole)
return category->name();
if (role == EffectsRole) {
QStringList effectsNames;
const QList<EffectNode *> effects = category->effects();
const QList<EffectNode *> effects = category->nodes();
for (const EffectNode *effect : effects)
effectsNames << effect->name();
@@ -90,7 +90,7 @@ void EffectMakerModel::loadModel()
itEffects.next();
effects.push_back(new EffectNode(QFileInfo(itEffects.fileName()).baseName()));
}
EffectsCategory *category = new EffectsCategory(itCategories.fileName(), effects);
EffectNodesCategory *category = new EffectNodesCategory(itCategories.fileName(), effects);
m_categories.push_back(category);
endInsertRows();
}

View File

@@ -5,7 +5,7 @@
#include <QStandardItemModel>
#include "effectscategory.h"
#include "effectnodescategory.h"
namespace Utils {
class FilePath;
@@ -37,7 +37,7 @@ public:
void loadModel();
void resetModel();
QList<EffectsCategory *> categories() { return m_categories; }
QList<EffectNodesCategory *> categories() { return m_categories; }
Q_INVOKABLE void selectEffect(int idx, bool force = false);
Q_INVOKABLE void applyToSelected(qint64 internalId, bool add = false);
@@ -51,7 +51,7 @@ private:
bool isValidIndex(int idx) const;
static Utils::FilePath getQmlEffectsPath();
QList<EffectsCategory *> m_categories;
QList<EffectNodesCategory *> m_categories;
int m_selectedIndex = -1;
bool m_isEmpty = true;

View File

@@ -0,0 +1,94 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectmakernodesmodel.h"
#include <projectexplorer/kit.h>
#include <projectexplorer/projecttree.h>
#include <projectexplorer/target.h>
#include <qtsupport/qtkitinformation.h>
#include <utils/filepath.h>
namespace QmlDesigner {
EffectMakerNodesModel::EffectMakerNodesModel(QObject *parent)
: QAbstractListModel{parent}
{
}
QHash<int, QByteArray> EffectMakerNodesModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[CategoryNameRole] = "categoryName";
roles[CategoryNodesRole] = "categoryNodes";
return roles;
}
int EffectMakerNodesModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_categories.count();
}
QVariant EffectMakerNodesModel::data(const QModelIndex &index, int role) const
{
QTC_ASSERT(index.isValid() && index.row() < m_categories.size(), return {});
QTC_ASSERT(roleNames().contains(role), return {});
return m_categories.at(index.row())->property(roleNames().value(role));
}
// static
Utils::FilePath EffectMakerNodesModel::getQmlEffectNodesPath()
{
const ProjectExplorer::Target *target = ProjectExplorer::ProjectTree::currentTarget();
if (!target) {
qWarning() << __FUNCTION__ << "No project open";
return "";
}
const QtSupport::QtVersion *baseQtVersion = QtSupport::QtKitAspect::qtVersion(target->kit());
return baseQtVersion->qmlPath().pathAppended("QtQuickEffectMaker/defaultnodes");
}
void EffectMakerNodesModel::loadModel()
{
const Utils::FilePath effectsPath = getQmlEffectNodesPath();
if (!effectsPath.exists()) {
qWarning() << __FUNCTION__ << "Effects not found.";
return;
}
QDirIterator itCategories(effectsPath.toString(), QDir::Dirs | QDir::NoDotAndDotDot);
while (itCategories.hasNext()) {
itCategories.next();
if (itCategories.fileName() == "images")
continue;
QList<EffectNode *> effects = {};
Utils::FilePath categoryPath = effectsPath.resolvePath(itCategories.fileName());
QDirIterator itEffects(categoryPath.toString(), QDir::Files | QDir::NoDotAndDotDot);
while (itEffects.hasNext()) {
itEffects.next();
effects.push_back(new EffectNode(QFileInfo(itEffects.fileName()).baseName()));
}
EffectNodesCategory *category = new EffectNodesCategory(itCategories.fileName(), effects);
m_categories.push_back(category);
}
resetModel();
}
void EffectMakerNodesModel::resetModel()
{
beginResetModel();
endResetModel();
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,43 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QStandardItemModel>
#include "effectnodescategory.h"
namespace Utils {
class FilePath;
}
namespace QmlDesigner {
class EffectMakerNodesModel : public QAbstractListModel
{
Q_OBJECT
enum Roles {
CategoryNameRole = Qt::UserRole + 1,
CategoryNodesRole
};
public:
EffectMakerNodesModel(QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex & parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void loadModel();
void resetModel();
QList<EffectNodesCategory *> categories() const { return m_categories; }
private:
static Utils::FilePath getQmlEffectNodesPath();
QList<EffectNodesCategory *> m_categories;
};
} // namespace QmlDesigner

View File

@@ -4,7 +4,7 @@
#include "effectmakerview.h"
#include "effectmakerwidget.h"
#include "effectmakermodel.h"
#include "effectmakernodesmodel.h"
#include "designmodecontext.h"
#include "nodeinstanceview.h"
@@ -57,7 +57,7 @@ void EffectMakerView::modelAttached(Model *model)
// Add some dummy effects data
//m_widget->effectMakerModel()->setEffects({"Drop Shadow", "Colorize", "Fast Blue"}); // TODO
m_widget->effectMakerModel()->loadModel();
m_widget->effectMakerNodesModel()->loadModel();
}
void EffectMakerView::modelAboutToBeDetached(Model *model)

View File

@@ -4,6 +4,7 @@
#include "effectmakerwidget.h"
#include "effectmakermodel.h"
#include "effectmakernodesmodel.h"
#include "effectmakerview.h"
#include "qmldesignerconstants.h"
#include "qmldesignerplugin.h"
@@ -33,6 +34,7 @@ static QString propertyEditorResourcesPath()
EffectMakerWidget::EffectMakerWidget(EffectMakerView *view)
: m_effectMakerModel{new EffectMakerModel(this)}
, m_effectMakerNodesModel{new EffectMakerNodesModel(this)}
, m_effectMakerView(view)
, m_quickWidget{new StudioQuickWidget(this)}
{
@@ -59,7 +61,8 @@ EffectMakerWidget::EffectMakerWidget(EffectMakerView *view)
QmlDesignerPlugin::trackWidgetFocusTime(this, Constants::EVENT_EFFECTMAKER_TIME);
auto map = m_quickWidget->registerPropertyMap("EffectMakerBackend");
map->setProperties({{"effectMakerModel", QVariant::fromValue(m_effectMakerModel.data())},
map->setProperties({{"effectMakerNodesModel", QVariant::fromValue(m_effectMakerNodesModel.data())},
{"effectMakerModel", QVariant::fromValue(m_effectMakerModel.data())},
{"rootView", QVariant::fromValue(this)}});
// init the first load of the QML UI elements
@@ -91,6 +94,11 @@ QPointer<EffectMakerModel> EffectMakerWidget::effectMakerModel() const
return m_effectMakerModel;
}
QPointer<EffectMakerNodesModel> EffectMakerWidget::effectMakerNodesModel() const
{
return m_effectMakerNodesModel;
}
void EffectMakerWidget::focusSection(int section)
{
Q_UNUSED(section)

View File

@@ -13,6 +13,7 @@ namespace QmlDesigner {
class EffectMakerView;
class EffectMakerModel;
class EffectMakerNodesModel;
class EffectMakerWidget : public QFrame
{
@@ -32,10 +33,10 @@ public:
StudioQuickWidget *quickWidget() const;
QPointer<EffectMakerModel> effectMakerModel() const;
QPointer<EffectMakerNodesModel> effectMakerNodesModel() const;
Q_INVOKABLE void focusSection(int section);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
@@ -43,6 +44,7 @@ private:
void reloadQmlSource();
QPointer<EffectMakerModel> m_effectMakerModel;
QPointer<EffectMakerNodesModel> m_effectMakerNodesModel;
QPointer<EffectMakerView> m_effectMakerView;
QPointer<StudioQuickWidget> m_quickWidget;
};

View File

@@ -7,8 +7,12 @@
namespace QmlDesigner {
class EffectNode
class EffectNode : public QObject
{
Q_OBJECT
Q_PROPERTY(QString nodeName MEMBER m_name CONSTANT)
public:
EffectNode(const QString &name);

View File

@@ -0,0 +1,22 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectnodescategory.h"
namespace QmlDesigner {
EffectNodesCategory::EffectNodesCategory(const QString &name, const QList<EffectNode *> &nodes)
: m_name(name),
m_categoryNodes(nodes) {}
QString EffectNodesCategory::name() const
{
return m_name;
}
QList<EffectNode *> EffectNodesCategory::nodes() const
{
return m_categoryNodes;
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,30 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "effectnode.h"
#include <QObject>
namespace QmlDesigner {
class EffectNodesCategory : public QObject
{
Q_OBJECT
Q_PROPERTY(QString categoryName MEMBER m_name CONSTANT)
Q_PROPERTY(QList<EffectNode *> categoryNodes MEMBER m_categoryNodes CONSTANT)
public:
EffectNodesCategory(const QString &name, const QList<EffectNode *> &nodes);
QString name() const;
QList<EffectNode *> nodes() const;
private:
QString m_name;
QList<EffectNode *> m_categoryNodes;
};
} // namespace QmlDesigner

View File

@@ -1,22 +0,0 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectscategory.h"
namespace QmlDesigner {
EffectsCategory::EffectsCategory(const QString &name, const QList<EffectNode *> &subcategories)
: m_name(name),
m_effects(subcategories) {}
QString EffectsCategory::name() const
{
return m_name;
}
QList<EffectNode *> EffectsCategory::effects() const
{
return m_effects;
}
} // namespace QmlDesigner

View File

@@ -1,25 +0,0 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "effectnode.h"
#include <QObject>
namespace QmlDesigner {
class EffectsCategory
{
public:
EffectsCategory(const QString &name, const QList<EffectNode *> &subcategories);
QString name() const;
QList<EffectNode *> effects() const;
private:
QString m_name;
QList<EffectNode *> m_effects;
};
} // namespace QmlDesigner