QmlDesigner: Add code editor for Effect Composer

Task-number: QDS-13443
Change-Id: I02c7a85336f283e0e55bab24459a91fa299abb40
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
Ali Kianian
2024-09-05 17:46:26 +03:00
parent 79ecc5e58c
commit 34f51df1ea
17 changed files with 725 additions and 21 deletions

View File

@@ -241,6 +241,9 @@
"LocalOrientIcon": { "LocalOrientIcon": {
"iconName": "localOrient_medium" "iconName": "localOrient_medium"
}, },
"LiveUpdateIcon": {
"iconName": "restartParticles_medium"
},
"MoveToolIcon": { "MoveToolIcon": {
"iconName": "move_medium" "iconName": "move_medium"
}, },
@@ -276,6 +279,9 @@
"SplitViewIcon": { "SplitViewIcon": {
"iconName": "splitScreen_medium" "iconName": "splitScreen_medium"
}, },
"SyncIcon": {
"iconName": "updateContent_medium"
},
"ToggleGroupIcon": { "ToggleGroupIcon": {
"Off": { "Off": {
"iconName": "selectOutline_medium" "iconName": "selectOutline_medium"

View File

@@ -195,6 +195,10 @@ Item {
onAssignToSelectedClicked: { onAssignToSelectedClicked: {
root.backendModel.assignToSelected() root.backendModel.assignToSelected()
} }
onOpenShadersCodeEditor: {
root.backendModel.openMainShadersCodeEditor()
}
} }
SplitView { SplitView {
@@ -366,6 +370,8 @@ Item {
expanded = wasExpanded expanded = wasExpanded
dragAnimation.enabled = true dragAnimation.enabled = true
} }
onOpenShadersCodeEditor: (idx) => root.backendModel.openShadersCodeEditor(idx)
} }
} // Repeater } // Repeater
} // Column } // Column

View File

@@ -19,6 +19,7 @@ Rectangle {
signal saveClicked signal saveClicked
signal saveAsClicked signal saveAsClicked
signal assignToSelectedClicked signal assignToSelectedClicked
signal openShadersCodeEditor
Row { Row {
spacing: 5 spacing: 5
@@ -48,12 +49,24 @@ Rectangle {
style: StudioTheme.Values.viewBarButtonStyle style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.saveAs_medium buttonIcon: StudioTheme.Constants.saveAs_medium
tooltip: qsTr("Save current composition with a new name") tooltip: qsTr("Save current composition with a new name")
enabled: root.backendModel ? root.backendModel.isEnabled && root.backendModel.currentComposition !== "" enabled: root.backendModel ? root.backendModel.isEnabled
&& root.backendModel.currentComposition !== ""
: false : false
onClicked: root.saveAsClicked() onClicked: root.saveAsClicked()
} }
HelperWidgets.AbstractButton {
style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.codeEditor_medium
tooltip: qsTr("Open Code")
enabled: root.backendModel ? root.backendModel.isEnabled
&& root.backendModel.currentComposition !== ""
: false
onClicked: root.openShadersCodeEditor()
}
HelperWidgets.AbstractButton { HelperWidgets.AbstractButton {
style: StudioTheme.Values.viewBarButtonStyle style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.assignTo_medium buttonIcon: StudioTheme.Constants.assignTo_medium

View File

@@ -30,10 +30,34 @@ HelperWidgets.Section {
eyeEnabled: nodeEnabled eyeEnabled: nodeEnabled
eyeButtonToolTip: qsTr("Enable/Disable Node") eyeButtonToolTip: qsTr("Enable/Disable Node")
signal openShadersCodeEditor(index: int)
onEyeButtonClicked: { onEyeButtonClicked: {
nodeEnabled = root.eyeEnabled nodeEnabled = root.eyeEnabled
} }
icons: HelperWidgets.IconButton {
icon: StudioTheme.Constants.codeEditor_medium
transparentBg: true
buttonSize: 21
iconSize: StudioTheme.Values.smallIconFontSize
iconColor: StudioTheme.Values.themeTextColor
iconScale: containsMouse ? 1.2 : 1
implicitWidth: width
onClicked: root.openShadersCodeEditor(index)
}
content: Label {
text: root.caption
color: root.labelColor
elide: Text.ElideRight
font.pixelSize: root.sectionFontSize
font.capitalization: root.labelCapitalization
anchors.verticalCenter: parent?.verticalCenter
textFormat: Text.RichText
leftPadding: StudioTheme.Values.toolbarSpacing
}
Column { Column {
spacing: 10 spacing: 10

View File

@@ -39,6 +39,8 @@ Item {
textFormat: Text.RichText textFormat: Text.RichText
} }
property Item icons
property int leftPadding: StudioTheme.Values.sectionLeftPadding property int leftPadding: StudioTheme.Values.sectionLeftPadding
property int rightPadding: 0 property int rightPadding: 0
property int topPadding: StudioTheme.Values.sectionHeadSpacerHeight property int topPadding: StudioTheme.Values.sectionHeadSpacerHeight
@@ -214,6 +216,13 @@ Item {
} }
} }
Item {
id: iconsContent
height: header.height
children: [ section.icons ]
Layout.preferredWidth: childrenRect.width
}
IconButton { IconButton {
id: arrow id: arrow
icon: StudioTheme.Constants.sectionToggle icon: StudioTheme.Constants.sectionToggle

View File

@@ -6,12 +6,14 @@ add_qtc_plugin(EffectComposer
Qt::Core Qt::CorePrivate Qt::Widgets Qt::Qml Qt::QmlPrivate Qt::Quick Qt::Core Qt::CorePrivate Qt::Widgets Qt::Qml Qt::QmlPrivate Qt::Quick
QtCreator::Utils QtCreator::Utils
SOURCES SOURCES
effectcodeeditorwidget.cpp effectcodeeditorwidget.h
effectcomposerplugin.cpp effectcomposerplugin.cpp
effectcomposerwidget.cpp effectcomposerwidget.h effectcomposerwidget.cpp effectcomposerwidget.h
effectcomposerview.cpp effectcomposerview.h effectcomposerview.cpp effectcomposerview.h
effectcomposermodel.cpp effectcomposermodel.h effectcomposermodel.cpp effectcomposermodel.h
effectcomposernodesmodel.cpp effectcomposernodesmodel.h effectcomposernodesmodel.cpp effectcomposernodesmodel.h
effectcomposeruniformsmodel.cpp effectcomposeruniformsmodel.h effectcomposeruniformsmodel.cpp effectcomposeruniformsmodel.h
effectshaderscodeeditor.cpp effectshaderscodeeditor.h
effectnode.cpp effectnode.h effectnode.cpp effectnode.h
effectnodescategory.cpp effectnodescategory.h effectnodescategory.cpp effectnodescategory.h
compositionnode.cpp compositionnode.h compositionnode.cpp compositionnode.h

View File

@@ -3,8 +3,9 @@
#include "compositionnode.h" #include "compositionnode.h"
#include "effectutils.h"
#include "effectcomposeruniformsmodel.h" #include "effectcomposeruniformsmodel.h"
#include "effectshaderscodeeditor.h"
#include "effectutils.h"
#include "propertyhandler.h" #include "propertyhandler.h"
#include "uniform.h" #include "uniform.h"
@@ -44,6 +45,8 @@ CompositionNode::CompositionNode(const QString &effectName, const QString &qenPa
} }
} }
CompositionNode::~CompositionNode() = default;
QString CompositionNode::fragmentCode() const QString CompositionNode::fragmentCode() const
{ {
return m_fragmentCode; return m_fragmentCode;
@@ -110,8 +113,8 @@ void CompositionNode::parse(const QString &effectName, const QString &qenPath, c
m_name = json.value("name").toString(); m_name = json.value("name").toString();
m_description = json.value("description").toString(); m_description = json.value("description").toString();
m_fragmentCode = EffectUtils::codeFromJsonArray(json.value("fragmentCode").toArray()); setFragmentCode(EffectUtils::codeFromJsonArray(json.value("fragmentCode").toArray()));
m_vertexCode = EffectUtils::codeFromJsonArray(json.value("vertexCode").toArray()); setVertexCode(EffectUtils::codeFromJsonArray(json.value("vertexCode").toArray()));
if (json.contains("extraMargin")) if (json.contains("extraMargin"))
m_extraMargin = json.value("extraMargin").toInt(); m_extraMargin = json.value("extraMargin").toInt();
@@ -154,6 +157,36 @@ void CompositionNode::parse(const QString &effectName, const QString &qenPath, c
} }
} }
void CompositionNode::ensureShadersCodeEditor()
{
if (m_shadersCodeEditor)
return;
m_shadersCodeEditor = Utils::makeUniqueObjectLatePtr<EffectShadersCodeEditor>(name());
m_shadersCodeEditor->setFragmentValue(fragmentCode());
m_shadersCodeEditor->setVertexValue(vertexCode());
connect(m_shadersCodeEditor.get(), &EffectShadersCodeEditor::vertexValueChanged, this, [this] {
setVertexCode(m_shadersCodeEditor->vertexValue());
});
connect(m_shadersCodeEditor.get(), &EffectShadersCodeEditor::fragmentValueChanged, this, [this] {
setFragmentCode(m_shadersCodeEditor->fragmentValue());
});
connect(
m_shadersCodeEditor.get(),
&EffectShadersCodeEditor::rebakeRequested,
this,
&CompositionNode::rebakeRequested);
}
void CompositionNode::requestRebakeIfLiveUpdateMode()
{
if (m_shadersCodeEditor && m_shadersCodeEditor->liveUpdate())
emit rebakeRequested();
}
QList<Uniform *> CompositionNode::uniforms() const QList<Uniform *> CompositionNode::uniforms() const
{ {
return m_uniforms; return m_uniforms;
@@ -189,6 +222,34 @@ void CompositionNode::setRefCount(int count)
emit isDepencyChanged(); emit isDepencyChanged();
} }
void CompositionNode::setFragmentCode(const QString &fragmentCode)
{
if (m_fragmentCode == fragmentCode)
return;
m_fragmentCode = fragmentCode;
emit fragmentCodeChanged();
requestRebakeIfLiveUpdateMode();
}
void CompositionNode::setVertexCode(const QString &vertexCode)
{
if (m_vertexCode == vertexCode)
return;
m_vertexCode = vertexCode;
emit vertexCodeChanged();
requestRebakeIfLiveUpdateMode();
}
void CompositionNode::openShadersCodeEditor()
{
ensureShadersCodeEditor();
m_shadersCodeEditor->showWidget();
}
QString CompositionNode::name() const QString CompositionNode::name() const
{ {
return m_name; return m_name;

View File

@@ -5,11 +5,15 @@
#include "effectcomposeruniformsmodel.h" #include "effectcomposeruniformsmodel.h"
#include <utils/uniqueobjectptr.h>
#include <QJsonObject> #include <QJsonObject>
#include <QObject> #include <QObject>
namespace EffectComposer { namespace EffectComposer {
class EffectShadersCodeEditor;
class CompositionNode : public QObject class CompositionNode : public QObject
{ {
Q_OBJECT Q_OBJECT
@@ -18,6 +22,12 @@ class CompositionNode : public QObject
Q_PROPERTY(bool nodeEnabled READ isEnabled WRITE setIsEnabled NOTIFY isEnabledChanged) Q_PROPERTY(bool nodeEnabled READ isEnabled WRITE setIsEnabled NOTIFY isEnabledChanged)
Q_PROPERTY(bool isDependency READ isDependency NOTIFY isDepencyChanged) Q_PROPERTY(bool isDependency READ isDependency NOTIFY isDepencyChanged)
Q_PROPERTY(QObject *nodeUniformsModel READ uniformsModel NOTIFY uniformsModelChanged) Q_PROPERTY(QObject *nodeUniformsModel READ uniformsModel NOTIFY uniformsModelChanged)
Q_PROPERTY(
QString fragmentCode
READ fragmentCode
WRITE setFragmentCode
NOTIFY fragmentCodeChanged)
Q_PROPERTY(QString vertexCode READ vertexCode WRITE setVertexCode NOTIFY vertexCodeChanged)
public: public:
enum NodeType { enum NodeType {
@@ -27,6 +37,7 @@ public:
}; };
CompositionNode(const QString &effectName, const QString &qenPath, const QJsonObject &json = {}); CompositionNode(const QString &effectName, const QString &qenPath, const QJsonObject &json = {});
virtual ~CompositionNode();
QString fragmentCode() const; QString fragmentCode() const;
QString vertexCode() const; QString vertexCode() const;
@@ -54,14 +65,23 @@ public:
int extraMargin() const { return m_extraMargin; } int extraMargin() const { return m_extraMargin; }
void setFragmentCode(const QString &fragmentCode);
void setVertexCode(const QString &vertexCode);
void openShadersCodeEditor();
signals: signals:
void uniformsModelChanged(); void uniformsModelChanged();
void isEnabledChanged(); void isEnabledChanged();
void isDepencyChanged(); void isDepencyChanged();
void rebakeRequested(); void rebakeRequested();
void fragmentCodeChanged();
void vertexCodeChanged();
private: private:
void parse(const QString &effectName, const QString &qenPath, const QJsonObject &json); void parse(const QString &effectName, const QString &qenPath, const QJsonObject &json);
void ensureShadersCodeEditor();
void requestRebakeIfLiveUpdateMode();
QString m_name; QString m_name;
NodeType m_type = CustomNode; NodeType m_type = CustomNode;
@@ -77,6 +97,7 @@ private:
QList<Uniform *> m_uniforms; QList<Uniform *> m_uniforms;
EffectComposerUniformsModel m_unifomrsModel; EffectComposerUniformsModel m_unifomrsModel;
Utils::UniqueObjectLatePtr<EffectShadersCodeEditor> m_shadersCodeEditor;
}; };
} // namespace EffectComposer } // namespace EffectComposer

View File

@@ -0,0 +1,142 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectcodeeditorwidget.h"
#include <qmldesigner/textmodifier/indentingtexteditormodifier.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/coreplugintr.h>
#include <coreplugin/icore.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <qmljseditor/qmljsautocompleter.h>
#include <qmljseditor/qmljscompletionassist.h>
#include <qmljseditor/qmljshighlighter.h>
#include <qmljseditor/qmljshoverhandler.h>
#include <qmljseditor/qmljssemantichighlighter.h>
#include <qmljstools/qmljsindenter.h>
#include <utils/mimeconstants.h>
#include <utils/transientscroll.h>
#include <QAction>
namespace EffectComposer {
constexpr char EFFECTEDITOR_CONTEXT_ID[] = "EffectEditor.EffectEditorContext";
EffectCodeEditorWidget::EffectCodeEditorWidget()
: m_context(new Core::IContext(this))
{
Core::Context context(EFFECTEDITOR_CONTEXT_ID, ProjectExplorer::Constants::QMLJS_LANGUAGE_ID);
m_context->setWidget(this);
m_context->setContext(context);
Core::ICore::addContextObject(m_context);
Utils::TransientScrollAreaSupport::support(this);
/*
* We have to register our own active auto completion shortcut, because the original shortcut will
* use the cursor position of the original editor in the editor manager.
*/
m_completionAction = new QAction(tr("Trigger Completion"), this);
Core::Command *command = Core::ActionManager::registerAction(
m_completionAction, TextEditor::Constants::COMPLETE_THIS, context);
command->setDefaultKeySequence(QKeySequence(
Core::useMacShortcuts
? tr("Meta+Space")
: tr("Ctrl+Space")));
connect(m_completionAction, &QAction::triggered, this, [this] {
invokeAssist(TextEditor::Completion);
});
}
EffectCodeEditorWidget::~EffectCodeEditorWidget()
{
unregisterAutoCompletion();
}
void EffectCodeEditorWidget::unregisterAutoCompletion()
{
if (m_completionAction) {
Core::ActionManager::unregisterAction(m_completionAction, TextEditor::Constants::COMPLETE_THIS);
delete m_completionAction;
m_completionAction = nullptr;
}
}
void EffectCodeEditorWidget::setEditorTextWithIndentation(const QString &text)
{
auto *doc = document();
doc->setPlainText(text);
// We don't need to indent an empty text but is also needed for safer text.length()-1 below
if (text.isEmpty())
return;
auto modifier = std::make_unique<QmlDesigner::IndentingTextEditModifier>(doc, QTextCursor{doc});
modifier->indent(0, text.length()-1);
}
EffectDocument::EffectDocument()
: QmlJSEditor::QmlJSEditorDocument(EFFECTEDITOR_CONTEXT_ID)
, m_semanticHighlighter(new QmlJSEditor::SemanticHighlighter(this))
{}
EffectDocument::~EffectDocument()
{
delete m_semanticHighlighter;
}
void EffectDocument::applyFontSettings()
{
TextDocument::applyFontSettings();
m_semanticHighlighter->updateFontSettings(fontSettings());
if (!isSemanticInfoOutdated() && semanticInfo().isValid())
m_semanticHighlighter->rerun(semanticInfo());
}
void EffectDocument::triggerPendingUpdates()
{
TextDocument::triggerPendingUpdates(); // Calls applyFontSettings if necessary
if (!isSemanticInfoOutdated() && semanticInfo().isValid())
m_semanticHighlighter->rerun(semanticInfo());
}
EffectCodeEditorFactory::EffectCodeEditorFactory()
{
setId(EFFECTEDITOR_CONTEXT_ID);
setDisplayName(::Core::Tr::tr("Effect Code Editor"));
addMimeType(EFFECTEDITOR_CONTEXT_ID);
addMimeType(Utils::Constants::QML_MIMETYPE);
addMimeType(Utils::Constants::QMLTYPES_MIMETYPE);
addMimeType(Utils::Constants::JS_MIMETYPE);
setDocumentCreator([]() { return new EffectDocument; });
setEditorWidgetCreator([]() { return new EffectCodeEditorWidget; });
setEditorCreator([]() { return new QmlJSEditor::QmlJSEditor; });
setAutoCompleterCreator([]() { return new QmlJSEditor::AutoCompleter; });
setCommentDefinition(Utils::CommentDefinition::CppStyle);
setParenthesesMatchingEnabled(true);
setCodeFoldingSupported(true);
addHoverHandler(new QmlJSEditor::QmlJSHoverHandler);
setCompletionAssistProvider(new QmlJSEditor::QmlJSCompletionAssistProvider);
}
void EffectCodeEditorFactory::decorateEditor(TextEditor::TextEditorWidget *editor)
{
editor->textDocument()->resetSyntaxHighlighter(
[] { return new QmlJSEditor::QmlJSHighlighter(); });
editor->textDocument()->setIndenter(QmlJSEditor::createQmlJsIndenter(
editor->textDocument()->document()));
editor->setAutoCompleter(new QmlJSEditor::AutoCompleter);
}
} // namespace EffectComposer

View File

@@ -0,0 +1,63 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <qmljseditor/qmljseditor.h>
#include <qmljseditor/qmljseditordocument.h>
QT_FORWARD_DECLARE_CLASS(QAction)
namespace QmlJSEditor {
class SemanticHighlighter;
}
namespace Core {
class IContext;
}
namespace EffectComposer {
class EffectCodeEditorWidget : public QmlJSEditor::QmlJSEditorWidget
{
Q_OBJECT
public:
EffectCodeEditorWidget();
~EffectCodeEditorWidget() override;
void unregisterAutoCompletion();
void setEditorTextWithIndentation(const QString &text);
signals:
void returnKeyClicked();
public:
Core::IContext *m_context = nullptr;
QAction *m_completionAction = nullptr;
bool m_isMultiline = true;
};
class EffectDocument : public QmlJSEditor::QmlJSEditorDocument
{
public:
EffectDocument();
~EffectDocument();
protected:
void applyFontSettings() final;
void triggerPendingUpdates() final;
private:
QmlJSEditor::SemanticHighlighter *m_semanticHighlighter = nullptr;
};
class EffectCodeEditorFactory : public TextEditor::TextEditorFactory
{
public:
EffectCodeEditorFactory();
static void decorateEditor(TextEditor::TextEditorWidget *editor);
};
} // namespace EffectComposer

View File

@@ -4,6 +4,7 @@
#include "effectcomposermodel.h" #include "effectcomposermodel.h"
#include "compositionnode.h" #include "compositionnode.h"
#include "effectshaderscodeeditor.h"
#include "effectutils.h" #include "effectutils.h"
#include "propertyhandler.h" #include "propertyhandler.h"
#include "syntaxhighlighterdata.h" #include "syntaxhighlighterdata.h"
@@ -252,6 +253,8 @@ void EffectComposerModel::setFragmentShader(const QString &newFragmentShader)
return; return;
m_fragmentShader = newFragmentShader; m_fragmentShader = newFragmentShader;
rebakeIfLiveUpdateMode();
} }
QString EffectComposerModel::vertexShader() const QString EffectComposerModel::vertexShader() const
@@ -265,6 +268,8 @@ void EffectComposerModel::setVertexShader(const QString &newVertexShader)
return; return;
m_vertexShader = newVertexShader; m_vertexShader = newVertexShader;
rebakeIfLiveUpdateMode();
} }
QString EffectComposerModel::qmlComponentString() const QString EffectComposerModel::qmlComponentString() const
@@ -990,6 +995,50 @@ void EffectComposerModel::saveComposition(const QString &name)
setHasUnsavedChanges(false); setHasUnsavedChanges(false);
} }
void EffectComposerModel::openShadersCodeEditor(int idx)
{
if (m_nodes.size() < idx || idx < 0)
return;
CompositionNode *node = m_nodes.at(idx);
node->openShadersCodeEditor();
}
void EffectComposerModel::openMainShadersCodeEditor()
{
if (!m_shadersCodeEditor) {
m_shadersCodeEditor = Utils::makeUniqueObjectLatePtr<EffectShadersCodeEditor>(
currentComposition());
m_shadersCodeEditor->setFragmentValue(generateFragmentShader(true));
m_shadersCodeEditor->setVertexValue(generateVertexShader(true));
connect(
m_shadersCodeEditor.get(),
&EffectShadersCodeEditor::vertexValueChanged,
this,
[this] {
setVertexShader(m_shadersCodeEditor->vertexValue());
setHasUnsavedChanges(true);
});
connect(
m_shadersCodeEditor.get(),
&EffectShadersCodeEditor::fragmentValueChanged,
this,
[this] {
setFragmentShader(m_shadersCodeEditor->fragmentValue());
setHasUnsavedChanges(true);
});
connect(
m_shadersCodeEditor.get(),
&EffectShadersCodeEditor::rebakeRequested,
this,
&EffectComposerModel::startRebakeTimer);
}
m_shadersCodeEditor->showWidget();
}
void EffectComposerModel::openComposition(const QString &path) void EffectComposerModel::openComposition(const QString &path)
{ {
clear(true); clear(true);
@@ -1828,7 +1877,6 @@ void EffectComposerModel::bakeShaders()
runQsb(qsbPath, outPaths, false); runQsb(qsbPath, outPaths, false);
runQsb(qsbPrevPath, outPrevPaths, true); runQsb(qsbPrevPath, outPrevPaths, true);
} }
bool EffectComposerModel::shadersUpToDate() const bool EffectComposerModel::shadersUpToDate() const
@@ -2003,14 +2051,16 @@ QString EffectComposerModel::getQmlComponentString(bool localFiles)
void EffectComposerModel::connectCompositionNode(CompositionNode *node) void EffectComposerModel::connectCompositionNode(CompositionNode *node)
{ {
connect(qobject_cast<EffectComposerUniformsModel *>(node->uniformsModel()), auto setUnsaved = std::bind(&EffectComposerModel::setHasUnsavedChanges, this, true);
&EffectComposerUniformsModel::dataChanged, this, [this] { connect(
setHasUnsavedChanges(true); qobject_cast<EffectComposerUniformsModel *>(node->uniformsModel()),
}); &EffectComposerUniformsModel::dataChanged,
connect(node, &CompositionNode::rebakeRequested, this, [this] { this,
// This can come multiple times in a row in response to property changes, so let's buffer it setUnsaved);
m_rebakeTimer.start(200);
}); connect(node, &CompositionNode::rebakeRequested, this, &EffectComposerModel::startRebakeTimer);
connect(node, &CompositionNode::fragmentCodeChanged, this, setUnsaved);
connect(node, &CompositionNode::vertexCodeChanged, this, setUnsaved);
} }
void EffectComposerModel::updateExtraMargin() void EffectComposerModel::updateExtraMargin()
@@ -2020,6 +2070,18 @@ void EffectComposerModel::updateExtraMargin()
m_extraMargin = qMax(node->extraMargin(), m_extraMargin); m_extraMargin = qMax(node->extraMargin(), m_extraMargin);
} }
void EffectComposerModel::startRebakeTimer()
{
// This can come multiple times in a row in response to property changes, so let's buffer it
m_rebakeTimer.start(200);
}
void EffectComposerModel::rebakeIfLiveUpdateMode()
{
if (m_shadersCodeEditor && m_shadersCodeEditor->liveUpdate())
startRebakeTimer();
}
QSet<QByteArray> EffectComposerModel::getExposedProperties(const QByteArray &qmlContent) QSet<QByteArray> EffectComposerModel::getExposedProperties(const QByteArray &qmlContent)
{ {
QSet<QByteArray> returnSet; QSet<QByteArray> returnSet;
@@ -2051,6 +2113,8 @@ void EffectComposerModel::setCurrentComposition(const QString &newCurrentComposi
m_currentComposition = newCurrentComposition; m_currentComposition = newCurrentComposition;
emit currentCompositionChanged(); emit currentCompositionChanged();
m_shadersCodeEditor.reset();
} }
Utils::FilePath EffectComposerModel::compositionPath() const Utils::FilePath EffectComposerModel::compositionPath() const

View File

@@ -6,6 +6,7 @@
#include "shaderfeatures.h" #include "shaderfeatures.h"
#include <utils/filepath.h> #include <utils/filepath.h>
#include <utils/uniqueobjectptr.h>
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QFileSystemWatcher> #include <QFileSystemWatcher>
@@ -26,6 +27,7 @@ class Process;
namespace EffectComposer { namespace EffectComposer {
class CompositionNode; class CompositionNode;
class EffectShadersCodeEditor;
class Uniform; class Uniform;
struct EffectError { struct EffectError {
@@ -100,6 +102,9 @@ public:
Q_INVOKABLE void saveComposition(const QString &name); Q_INVOKABLE void saveComposition(const QString &name);
Q_INVOKABLE void openShadersCodeEditor(int idx);
Q_INVOKABLE void openMainShadersCodeEditor();
void openComposition(const QString &path); void openComposition(const QString &path);
QString currentComposition() const; QString currentComposition() const;
@@ -190,6 +195,8 @@ private:
void connectCompositionNode(CompositionNode *node); void connectCompositionNode(CompositionNode *node);
void updateExtraMargin(); void updateExtraMargin();
void startRebakeTimer();
void rebakeIfLiveUpdateMode();
QSet<QByteArray> getExposedProperties(const QByteArray &qmlContent); QSet<QByteArray> getExposedProperties(const QByteArray &qmlContent);
QList<CompositionNode *> m_nodes; QList<CompositionNode *> m_nodes;
@@ -230,6 +237,7 @@ private:
int m_extraMargin = 0; int m_extraMargin = 0;
QString m_effectTypePrefix; QString m_effectTypePrefix;
Utils::FilePath m_compositionPath; Utils::FilePath m_compositionPath;
Utils::UniqueObjectLatePtr<EffectShadersCodeEditor> m_shadersCodeEditor;
const QRegularExpression m_spaceReg = QRegularExpression("\\s+"); const QRegularExpression m_spaceReg = QRegularExpression("\\s+");
}; };

View File

@@ -0,0 +1,226 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "effectshaderscodeeditor.h"
#include "effectcodeeditorwidget.h"
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
#include <componentcore/designeractionmanager.h>
#include <componentcore/designericons.h>
#include <componentcore/theme.h>
#include <qmldesigner/qmldesignerplugin.h>
#include <qmljseditor/qmljseditor.h>
#include <qmljseditor/qmljseditordocument.h>
#include <qmldesignerplugin.h>
#include <QPlainTextEdit>
#include <QSettings>
#include <QTabWidget>
#include <QToolBar>
#include <QVBoxLayout>
namespace {
using IconId = QmlDesigner::DesignerIcons::IconId;
inline constexpr char EFFECTCOMPOSER_LIVE_UPDATE_KEY[] = "EffectComposer/CodeEditor/LiveUpdate";
QIcon toolbarIcon(IconId iconId)
{
return QmlDesigner::DesignerActionManager::instance().toolbarIcon(iconId);
};
} // namespace
namespace EffectComposer {
EffectShadersCodeEditor::EffectShadersCodeEditor(const QString &title, QWidget *parent)
: QWidget(parent)
, m_settings(new QSettings(qApp->organizationName(), qApp->applicationName(), this))
{
setWindowFlag(Qt::Tool, true);
setWindowTitle(title);
m_fragmentEditor = createJSEditor();
m_vertexEditor = createJSEditor();
connect(
m_fragmentEditor,
&QPlainTextEdit::textChanged,
this,
&EffectShadersCodeEditor::fragmentValueChanged);
connect(
m_vertexEditor,
&QPlainTextEdit::textChanged,
this,
&EffectShadersCodeEditor::vertexValueChanged);
setupUIComponents();
}
EffectShadersCodeEditor::~EffectShadersCodeEditor()
{
m_fragmentEditor->deleteLater();
m_vertexEditor->deleteLater();
}
void EffectShadersCodeEditor::showWidget()
{
readAndApplyLiveUpdateSettings();
show();
raise();
m_vertexEditor->setFocus();
}
void EffectShadersCodeEditor::showWidget(int x, int y)
{
showWidget();
move(QPoint(x, y));
}
QString EffectShadersCodeEditor::fragmentValue() const
{
if (!m_fragmentEditor)
return {};
return m_fragmentEditor->document()->toPlainText();
}
void EffectShadersCodeEditor::setFragmentValue(const QString &text)
{
if (m_fragmentEditor)
m_fragmentEditor->setEditorTextWithIndentation(text);
}
QString EffectShadersCodeEditor::vertexValue() const
{
if (!m_vertexEditor)
return {};
return m_vertexEditor->document()->toPlainText();
}
void EffectShadersCodeEditor::setVertexValue(const QString &text)
{
if (m_vertexEditor)
m_vertexEditor->setEditorTextWithIndentation(text);
}
bool EffectShadersCodeEditor::liveUpdate() const
{
return m_liveUpdate;
}
void EffectShadersCodeEditor::setLiveUpdate(bool liveUpdate)
{
if (m_liveUpdate == liveUpdate)
return;
m_liveUpdate = liveUpdate;
writeLiveUpdateSettings();
emit liveUpdateChanged(m_liveUpdate);
if (m_liveUpdate)
emit rebakeRequested();
}
EffectCodeEditorWidget *EffectShadersCodeEditor::createJSEditor()
{
static EffectCodeEditorFactory f;
TextEditor::BaseTextEditor *editor = qobject_cast<TextEditor::BaseTextEditor *>(
f.createEditor());
Q_ASSERT(editor);
editor->setParent(this);
EffectCodeEditorWidget *editorWidget = qobject_cast<EffectCodeEditorWidget *>(
editor->editorWidget());
Q_ASSERT(editorWidget);
editorWidget->setLineNumbersVisible(false);
editorWidget->setMarksVisible(false);
editorWidget->setCodeFoldingSupported(false);
editorWidget->setTabChangesFocus(true);
editorWidget->unregisterAutoCompletion();
editorWidget->setParent(this);
editorWidget->setFrameStyle(QFrame::StyledPanel | QFrame::Raised);
return editorWidget;
}
void EffectShadersCodeEditor::setupUIComponents()
{
QVBoxLayout *verticalLayout = new QVBoxLayout(this);
QTabWidget *tabWidget = new QTabWidget(this);
tabWidget->addTab(m_fragmentEditor, tr("Fragment Shader"));
tabWidget->addTab(m_vertexEditor, tr("Vertex Shader"));
verticalLayout->setContentsMargins(0, 0, 0, 0);
verticalLayout->addWidget(createToolbar());
verticalLayout->addWidget(tabWidget);
this->resize(660, 240);
}
void EffectShadersCodeEditor::closeEvent(QCloseEvent *event)
{
QWidget::closeEvent(event);
if (!liveUpdate())
emit rebakeRequested();
}
void EffectShadersCodeEditor::writeLiveUpdateSettings()
{
m_settings->setValue(EFFECTCOMPOSER_LIVE_UPDATE_KEY, m_liveUpdate);
}
void EffectShadersCodeEditor::readAndApplyLiveUpdateSettings()
{
bool liveUpdateStatus = m_settings->value(EFFECTCOMPOSER_LIVE_UPDATE_KEY, false).toBool();
setLiveUpdate(liveUpdateStatus);
}
QToolBar *EffectShadersCodeEditor::createToolbar()
{
using QmlDesigner::Theme;
QToolBar *toolbar = new QToolBar(this);
toolbar->setFixedHeight(Theme::toolbarSize());
toolbar->setFloatable(false);
toolbar->setContentsMargins(0, 0, 0, 0);
toolbar->setStyleSheet(Theme::replaceCssColors(
QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"))));
QAction *liveUpdateButton
= toolbar->addAction(toolbarIcon(IconId::LiveUpdateIcon), tr("Live Update"));
liveUpdateButton->setCheckable(true);
connect(liveUpdateButton, &QAction::toggled, this, &EffectShadersCodeEditor::setLiveUpdate);
QAction *applyAction = toolbar->addAction(toolbarIcon(IconId::SyncIcon), tr("Apply"));
connect(applyAction, &QAction::triggered, this, &EffectShadersCodeEditor::rebakeRequested);
auto syncLive = [liveUpdateButton, applyAction](bool liveState) {
liveUpdateButton->setChecked(liveState);
applyAction->setDisabled(liveState);
};
connect(this, &EffectShadersCodeEditor::liveUpdateChanged, this, syncLive);
syncLive(liveUpdate());
toolbar->addAction(liveUpdateButton);
toolbar->addAction(applyAction);
return toolbar;
}
} // namespace EffectComposer

View File

@@ -0,0 +1,61 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <texteditor/texteditor.h>
QT_FORWARD_DECLARE_CLASS(QSettings)
QT_FORWARD_DECLARE_CLASS(QToolBar)
namespace EffectComposer {
class EffectCodeEditorWidget;
class EffectShadersCodeEditor : public QWidget
{
Q_OBJECT
Q_PROPERTY(bool liveUpdate READ liveUpdate WRITE setLiveUpdate NOTIFY liveUpdateChanged)
public:
EffectShadersCodeEditor(const QString &title = tr("Untitled Editor"), QWidget *parent = nullptr);
~EffectShadersCodeEditor() override;
void showWidget();
void showWidget(int x, int y);
QString fragmentValue() const;
void setFragmentValue(const QString &text);
QString vertexValue() const;
void setVertexValue(const QString &text);
bool liveUpdate() const;
void setLiveUpdate(bool liveUpdate);
signals:
void liveUpdateChanged(bool);
void fragmentValueChanged();
void vertexValueChanged();
void rebakeRequested();
protected:
using QWidget::show;
EffectCodeEditorWidget *createJSEditor();
void setupUIComponents();
void closeEvent(QCloseEvent *event) override;
private:
void writeLiveUpdateSettings();
void readAndApplyLiveUpdateSettings();
QToolBar *createToolbar();
QSettings *m_settings = nullptr;
QPointer<EffectCodeEditorWidget> m_fragmentEditor;
QPointer<EffectCodeEditorWidget> m_vertexEditor;
bool m_liveUpdate = false;
};
} // namespace EffectComposer

View File

@@ -21,7 +21,6 @@
#include <qmljstools/qmljsindenter.h> #include <qmljstools/qmljsindenter.h>
#include <utils/fancylineedit.h>
#include <utils/mimeconstants.h> #include <utils/mimeconstants.h>
#include <utils/transientscroll.h> #include <utils/transientscroll.h>

View File

@@ -80,6 +80,7 @@ public:
LightDirectionalIcon, LightDirectionalIcon,
LightPointIcon, LightPointIcon,
LightSpotIcon, LightSpotIcon,
LiveUpdateIcon,
LocalOrientIcon, LocalOrientIcon,
MakeComponentIcon, MakeComponentIcon,
MaterialIcon, MaterialIcon,
@@ -107,6 +108,7 @@ public:
SnappingIcon, SnappingIcon,
SnappingConfIcon, SnappingConfIcon,
SplitViewIcon, SplitViewIcon,
SyncIcon,
TimelineIcon, TimelineIcon,
ToggleGroupIcon, ToggleGroupIcon,
VisibilityIcon VisibilityIcon

View File

@@ -1759,13 +1759,10 @@ void editInEffectComposer(const SelectionContext &selectionContext)
bool isEffectComposerActivated() bool isEffectComposerActivated()
{ {
const ExtensionSystem::PluginSpecs specs = ExtensionSystem::PluginManager::plugins(); using namespace ExtensionSystem;
return std::ranges::find_if(specs, return Utils::anyOf(PluginManager::plugins(), [](PluginSpec *spec) {
[](ExtensionSystem::PluginSpec *spec) { return spec->name() == "EffectComposer" && spec->isEffectivelyEnabled();
return spec->name() == "EffectComposer" });
&& spec->isEffectivelyEnabled();
})
!= specs.end();
} }
void openEffectComposer(const QString &filePath) void openEffectComposer(const QString &filePath)