forked from qt-creator/qt-creator
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:
@@ -241,6 +241,9 @@
|
||||
"LocalOrientIcon": {
|
||||
"iconName": "localOrient_medium"
|
||||
},
|
||||
"LiveUpdateIcon": {
|
||||
"iconName": "restartParticles_medium"
|
||||
},
|
||||
"MoveToolIcon": {
|
||||
"iconName": "move_medium"
|
||||
},
|
||||
@@ -276,6 +279,9 @@
|
||||
"SplitViewIcon": {
|
||||
"iconName": "splitScreen_medium"
|
||||
},
|
||||
"SyncIcon": {
|
||||
"iconName": "updateContent_medium"
|
||||
},
|
||||
"ToggleGroupIcon": {
|
||||
"Off": {
|
||||
"iconName": "selectOutline_medium"
|
||||
|
@@ -195,6 +195,10 @@ Item {
|
||||
onAssignToSelectedClicked: {
|
||||
root.backendModel.assignToSelected()
|
||||
}
|
||||
|
||||
onOpenShadersCodeEditor: {
|
||||
root.backendModel.openMainShadersCodeEditor()
|
||||
}
|
||||
}
|
||||
|
||||
SplitView {
|
||||
@@ -366,6 +370,8 @@ Item {
|
||||
expanded = wasExpanded
|
||||
dragAnimation.enabled = true
|
||||
}
|
||||
|
||||
onOpenShadersCodeEditor: (idx) => root.backendModel.openShadersCodeEditor(idx)
|
||||
}
|
||||
} // Repeater
|
||||
} // Column
|
||||
|
@@ -19,6 +19,7 @@ Rectangle {
|
||||
signal saveClicked
|
||||
signal saveAsClicked
|
||||
signal assignToSelectedClicked
|
||||
signal openShadersCodeEditor
|
||||
|
||||
Row {
|
||||
spacing: 5
|
||||
@@ -48,12 +49,24 @@ Rectangle {
|
||||
style: StudioTheme.Values.viewBarButtonStyle
|
||||
buttonIcon: StudioTheme.Constants.saveAs_medium
|
||||
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
|
||||
|
||||
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 {
|
||||
style: StudioTheme.Values.viewBarButtonStyle
|
||||
buttonIcon: StudioTheme.Constants.assignTo_medium
|
||||
|
@@ -30,10 +30,34 @@ HelperWidgets.Section {
|
||||
eyeEnabled: nodeEnabled
|
||||
eyeButtonToolTip: qsTr("Enable/Disable Node")
|
||||
|
||||
signal openShadersCodeEditor(index: int)
|
||||
|
||||
onEyeButtonClicked: {
|
||||
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 {
|
||||
spacing: 10
|
||||
|
||||
|
@@ -39,6 +39,8 @@ Item {
|
||||
textFormat: Text.RichText
|
||||
}
|
||||
|
||||
property Item icons
|
||||
|
||||
property int leftPadding: StudioTheme.Values.sectionLeftPadding
|
||||
property int rightPadding: 0
|
||||
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 {
|
||||
id: arrow
|
||||
icon: StudioTheme.Constants.sectionToggle
|
||||
|
@@ -6,12 +6,14 @@ add_qtc_plugin(EffectComposer
|
||||
Qt::Core Qt::CorePrivate Qt::Widgets Qt::Qml Qt::QmlPrivate Qt::Quick
|
||||
QtCreator::Utils
|
||||
SOURCES
|
||||
effectcodeeditorwidget.cpp effectcodeeditorwidget.h
|
||||
effectcomposerplugin.cpp
|
||||
effectcomposerwidget.cpp effectcomposerwidget.h
|
||||
effectcomposerview.cpp effectcomposerview.h
|
||||
effectcomposermodel.cpp effectcomposermodel.h
|
||||
effectcomposernodesmodel.cpp effectcomposernodesmodel.h
|
||||
effectcomposeruniformsmodel.cpp effectcomposeruniformsmodel.h
|
||||
effectshaderscodeeditor.cpp effectshaderscodeeditor.h
|
||||
effectnode.cpp effectnode.h
|
||||
effectnodescategory.cpp effectnodescategory.h
|
||||
compositionnode.cpp compositionnode.h
|
||||
|
@@ -3,8 +3,9 @@
|
||||
|
||||
#include "compositionnode.h"
|
||||
|
||||
#include "effectutils.h"
|
||||
#include "effectcomposeruniformsmodel.h"
|
||||
#include "effectshaderscodeeditor.h"
|
||||
#include "effectutils.h"
|
||||
#include "propertyhandler.h"
|
||||
#include "uniform.h"
|
||||
|
||||
@@ -44,6 +45,8 @@ CompositionNode::CompositionNode(const QString &effectName, const QString &qenPa
|
||||
}
|
||||
}
|
||||
|
||||
CompositionNode::~CompositionNode() = default;
|
||||
|
||||
QString CompositionNode::fragmentCode() const
|
||||
{
|
||||
return m_fragmentCode;
|
||||
@@ -110,8 +113,8 @@ void CompositionNode::parse(const QString &effectName, const QString &qenPath, c
|
||||
|
||||
m_name = json.value("name").toString();
|
||||
m_description = json.value("description").toString();
|
||||
m_fragmentCode = EffectUtils::codeFromJsonArray(json.value("fragmentCode").toArray());
|
||||
m_vertexCode = EffectUtils::codeFromJsonArray(json.value("vertexCode").toArray());
|
||||
setFragmentCode(EffectUtils::codeFromJsonArray(json.value("fragmentCode").toArray()));
|
||||
setVertexCode(EffectUtils::codeFromJsonArray(json.value("vertexCode").toArray()));
|
||||
|
||||
if (json.contains("extraMargin"))
|
||||
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
|
||||
{
|
||||
return m_uniforms;
|
||||
@@ -189,6 +222,34 @@ void CompositionNode::setRefCount(int count)
|
||||
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
|
||||
{
|
||||
return m_name;
|
||||
|
@@ -5,11 +5,15 @@
|
||||
|
||||
#include "effectcomposeruniformsmodel.h"
|
||||
|
||||
#include <utils/uniqueobjectptr.h>
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
|
||||
namespace EffectComposer {
|
||||
|
||||
class EffectShadersCodeEditor;
|
||||
|
||||
class CompositionNode : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -18,6 +22,12 @@ class CompositionNode : public QObject
|
||||
Q_PROPERTY(bool nodeEnabled READ isEnabled WRITE setIsEnabled NOTIFY isEnabledChanged)
|
||||
Q_PROPERTY(bool isDependency READ isDependency NOTIFY isDepencyChanged)
|
||||
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:
|
||||
enum NodeType {
|
||||
@@ -27,6 +37,7 @@ public:
|
||||
};
|
||||
|
||||
CompositionNode(const QString &effectName, const QString &qenPath, const QJsonObject &json = {});
|
||||
virtual ~CompositionNode();
|
||||
|
||||
QString fragmentCode() const;
|
||||
QString vertexCode() const;
|
||||
@@ -54,14 +65,23 @@ public:
|
||||
|
||||
int extraMargin() const { return m_extraMargin; }
|
||||
|
||||
void setFragmentCode(const QString &fragmentCode);
|
||||
void setVertexCode(const QString &vertexCode);
|
||||
|
||||
void openShadersCodeEditor();
|
||||
|
||||
signals:
|
||||
void uniformsModelChanged();
|
||||
void isEnabledChanged();
|
||||
void isDepencyChanged();
|
||||
void rebakeRequested();
|
||||
void fragmentCodeChanged();
|
||||
void vertexCodeChanged();
|
||||
|
||||
private:
|
||||
void parse(const QString &effectName, const QString &qenPath, const QJsonObject &json);
|
||||
void ensureShadersCodeEditor();
|
||||
void requestRebakeIfLiveUpdateMode();
|
||||
|
||||
QString m_name;
|
||||
NodeType m_type = CustomNode;
|
||||
@@ -77,6 +97,7 @@ private:
|
||||
QList<Uniform *> m_uniforms;
|
||||
|
||||
EffectComposerUniformsModel m_unifomrsModel;
|
||||
Utils::UniqueObjectLatePtr<EffectShadersCodeEditor> m_shadersCodeEditor;
|
||||
};
|
||||
|
||||
} // namespace EffectComposer
|
||||
|
142
src/plugins/effectcomposer/effectcodeeditorwidget.cpp
Normal file
142
src/plugins/effectcomposer/effectcodeeditorwidget.cpp
Normal 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
|
63
src/plugins/effectcomposer/effectcodeeditorwidget.h
Normal file
63
src/plugins/effectcomposer/effectcodeeditorwidget.h
Normal 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
|
@@ -4,6 +4,7 @@
|
||||
#include "effectcomposermodel.h"
|
||||
|
||||
#include "compositionnode.h"
|
||||
#include "effectshaderscodeeditor.h"
|
||||
#include "effectutils.h"
|
||||
#include "propertyhandler.h"
|
||||
#include "syntaxhighlighterdata.h"
|
||||
@@ -252,6 +253,8 @@ void EffectComposerModel::setFragmentShader(const QString &newFragmentShader)
|
||||
return;
|
||||
|
||||
m_fragmentShader = newFragmentShader;
|
||||
|
||||
rebakeIfLiveUpdateMode();
|
||||
}
|
||||
|
||||
QString EffectComposerModel::vertexShader() const
|
||||
@@ -265,6 +268,8 @@ void EffectComposerModel::setVertexShader(const QString &newVertexShader)
|
||||
return;
|
||||
|
||||
m_vertexShader = newVertexShader;
|
||||
|
||||
rebakeIfLiveUpdateMode();
|
||||
}
|
||||
|
||||
QString EffectComposerModel::qmlComponentString() const
|
||||
@@ -990,6 +995,50 @@ void EffectComposerModel::saveComposition(const QString &name)
|
||||
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)
|
||||
{
|
||||
clear(true);
|
||||
@@ -1828,7 +1877,6 @@ void EffectComposerModel::bakeShaders()
|
||||
|
||||
runQsb(qsbPath, outPaths, false);
|
||||
runQsb(qsbPrevPath, outPrevPaths, true);
|
||||
|
||||
}
|
||||
|
||||
bool EffectComposerModel::shadersUpToDate() const
|
||||
@@ -2003,14 +2051,16 @@ QString EffectComposerModel::getQmlComponentString(bool localFiles)
|
||||
|
||||
void EffectComposerModel::connectCompositionNode(CompositionNode *node)
|
||||
{
|
||||
connect(qobject_cast<EffectComposerUniformsModel *>(node->uniformsModel()),
|
||||
&EffectComposerUniformsModel::dataChanged, this, [this] {
|
||||
setHasUnsavedChanges(true);
|
||||
});
|
||||
connect(node, &CompositionNode::rebakeRequested, this, [this] {
|
||||
// This can come multiple times in a row in response to property changes, so let's buffer it
|
||||
m_rebakeTimer.start(200);
|
||||
});
|
||||
auto setUnsaved = std::bind(&EffectComposerModel::setHasUnsavedChanges, this, true);
|
||||
connect(
|
||||
qobject_cast<EffectComposerUniformsModel *>(node->uniformsModel()),
|
||||
&EffectComposerUniformsModel::dataChanged,
|
||||
this,
|
||||
setUnsaved);
|
||||
|
||||
connect(node, &CompositionNode::rebakeRequested, this, &EffectComposerModel::startRebakeTimer);
|
||||
connect(node, &CompositionNode::fragmentCodeChanged, this, setUnsaved);
|
||||
connect(node, &CompositionNode::vertexCodeChanged, this, setUnsaved);
|
||||
}
|
||||
|
||||
void EffectComposerModel::updateExtraMargin()
|
||||
@@ -2020,6 +2070,18 @@ void EffectComposerModel::updateExtraMargin()
|
||||
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> returnSet;
|
||||
@@ -2051,6 +2113,8 @@ void EffectComposerModel::setCurrentComposition(const QString &newCurrentComposi
|
||||
|
||||
m_currentComposition = newCurrentComposition;
|
||||
emit currentCompositionChanged();
|
||||
|
||||
m_shadersCodeEditor.reset();
|
||||
}
|
||||
|
||||
Utils::FilePath EffectComposerModel::compositionPath() const
|
||||
|
@@ -6,6 +6,7 @@
|
||||
#include "shaderfeatures.h"
|
||||
|
||||
#include <utils/filepath.h>
|
||||
#include <utils/uniqueobjectptr.h>
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QFileSystemWatcher>
|
||||
@@ -26,6 +27,7 @@ class Process;
|
||||
namespace EffectComposer {
|
||||
|
||||
class CompositionNode;
|
||||
class EffectShadersCodeEditor;
|
||||
class Uniform;
|
||||
|
||||
struct EffectError {
|
||||
@@ -100,6 +102,9 @@ public:
|
||||
|
||||
Q_INVOKABLE void saveComposition(const QString &name);
|
||||
|
||||
Q_INVOKABLE void openShadersCodeEditor(int idx);
|
||||
Q_INVOKABLE void openMainShadersCodeEditor();
|
||||
|
||||
void openComposition(const QString &path);
|
||||
|
||||
QString currentComposition() const;
|
||||
@@ -190,6 +195,8 @@ private:
|
||||
|
||||
void connectCompositionNode(CompositionNode *node);
|
||||
void updateExtraMargin();
|
||||
void startRebakeTimer();
|
||||
void rebakeIfLiveUpdateMode();
|
||||
QSet<QByteArray> getExposedProperties(const QByteArray &qmlContent);
|
||||
|
||||
QList<CompositionNode *> m_nodes;
|
||||
@@ -230,6 +237,7 @@ private:
|
||||
int m_extraMargin = 0;
|
||||
QString m_effectTypePrefix;
|
||||
Utils::FilePath m_compositionPath;
|
||||
Utils::UniqueObjectLatePtr<EffectShadersCodeEditor> m_shadersCodeEditor;
|
||||
|
||||
const QRegularExpression m_spaceReg = QRegularExpression("\\s+");
|
||||
};
|
||||
|
226
src/plugins/effectcomposer/effectshaderscodeeditor.cpp
Normal file
226
src/plugins/effectcomposer/effectshaderscodeeditor.cpp
Normal 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
|
61
src/plugins/effectcomposer/effectshaderscodeeditor.h
Normal file
61
src/plugins/effectcomposer/effectshaderscodeeditor.h
Normal 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
|
@@ -21,7 +21,6 @@
|
||||
|
||||
#include <qmljstools/qmljsindenter.h>
|
||||
|
||||
#include <utils/fancylineedit.h>
|
||||
#include <utils/mimeconstants.h>
|
||||
#include <utils/transientscroll.h>
|
||||
|
||||
|
@@ -80,6 +80,7 @@ public:
|
||||
LightDirectionalIcon,
|
||||
LightPointIcon,
|
||||
LightSpotIcon,
|
||||
LiveUpdateIcon,
|
||||
LocalOrientIcon,
|
||||
MakeComponentIcon,
|
||||
MaterialIcon,
|
||||
@@ -107,6 +108,7 @@ public:
|
||||
SnappingIcon,
|
||||
SnappingConfIcon,
|
||||
SplitViewIcon,
|
||||
SyncIcon,
|
||||
TimelineIcon,
|
||||
ToggleGroupIcon,
|
||||
VisibilityIcon
|
||||
|
@@ -1759,13 +1759,10 @@ void editInEffectComposer(const SelectionContext &selectionContext)
|
||||
|
||||
bool isEffectComposerActivated()
|
||||
{
|
||||
const ExtensionSystem::PluginSpecs specs = ExtensionSystem::PluginManager::plugins();
|
||||
return std::ranges::find_if(specs,
|
||||
[](ExtensionSystem::PluginSpec *spec) {
|
||||
return spec->name() == "EffectComposer"
|
||||
&& spec->isEffectivelyEnabled();
|
||||
})
|
||||
!= specs.end();
|
||||
using namespace ExtensionSystem;
|
||||
return Utils::anyOf(PluginManager::plugins(), [](PluginSpec *spec) {
|
||||
return spec->name() == "EffectComposer" && spec->isEffectivelyEnabled();
|
||||
});
|
||||
}
|
||||
|
||||
void openEffectComposer(const QString &filePath)
|
||||
|
Reference in New Issue
Block a user