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": {
"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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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

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 "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

View File

@@ -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+");
};

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 <utils/fancylineedit.h>
#include <utils/mimeconstants.h>
#include <utils/transientscroll.h>

View File

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

View File

@@ -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)