├── resources ├── qml │ ├── qmldir │ ├── uniforms │ │ ├── Sampler2d.qml │ │ ├── FloatUniform.qml │ │ ├── Vector2dUniform.qml │ │ ├── Vector3dUniform.qml │ │ ├── Sampler2dUniform.qml │ │ ├── Vector4dUniform.qml │ │ ├── UniformButton.qml │ │ ├── UniformSpinBox.qml │ │ ├── Matrix4x4Uniform.qml │ │ └── Uniform.qml │ ├── ShaderTextAreaHeader.qml │ ├── effects │ │ └── ShadowEffect.qml │ ├── LogWindow.qml │ ├── FloatingMenu.qml │ ├── CustomUniforms.qml │ ├── ShaderOutput.qml │ ├── main.qml │ ├── Dialogs.qml │ ├── FloatingButton.qml │ ├── ShaderTextArea.qml │ ├── TopMenu.qml │ ├── TopMenuButton.qml │ ├── TopMenuButton2.qml │ └── Shaders.qml └── assets │ ├── images │ ├── add.png │ ├── remove.png │ ├── up_arrow.png │ ├── down_arrow.png │ └── sample_image.jpg │ └── fonts │ ├── consolas-bold.TTF │ ├── consolas-italic.ttf │ ├── consolas-regular.TTF │ └── consolas-bold-italic.ttf ├── README.md ├── Dictionary.h ├── .gitignore ├── Info.plist ├── LICENSE ├── ShaderEditor.pro ├── GlslHighlighter.h ├── main.cpp ├── DynamicPropertyModel.h ├── DynamicPropertyHandler.h ├── qml.qrc ├── DynamicPropertyModel.cpp ├── DynamicPropertyHandler.cpp ├── Dictionary.cpp └── GlslHighlighter.cpp /resources/qml/qmldir: -------------------------------------------------------------------------------- 1 | singleton Shaders 1.0 Shaders.qml 2 | -------------------------------------------------------------------------------- /resources/assets/images/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmitrylis/ShaderEditor/HEAD/resources/assets/images/add.png -------------------------------------------------------------------------------- /resources/assets/images/remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmitrylis/ShaderEditor/HEAD/resources/assets/images/remove.png -------------------------------------------------------------------------------- /resources/assets/images/up_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmitrylis/ShaderEditor/HEAD/resources/assets/images/up_arrow.png -------------------------------------------------------------------------------- /resources/assets/fonts/consolas-bold.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmitrylis/ShaderEditor/HEAD/resources/assets/fonts/consolas-bold.TTF -------------------------------------------------------------------------------- /resources/assets/images/down_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmitrylis/ShaderEditor/HEAD/resources/assets/images/down_arrow.png -------------------------------------------------------------------------------- /resources/assets/images/sample_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmitrylis/ShaderEditor/HEAD/resources/assets/images/sample_image.jpg -------------------------------------------------------------------------------- /resources/assets/fonts/consolas-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmitrylis/ShaderEditor/HEAD/resources/assets/fonts/consolas-italic.ttf -------------------------------------------------------------------------------- /resources/assets/fonts/consolas-regular.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmitrylis/ShaderEditor/HEAD/resources/assets/fonts/consolas-regular.TTF -------------------------------------------------------------------------------- /resources/assets/fonts/consolas-bold-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmitrylis/ShaderEditor/HEAD/resources/assets/fonts/consolas-bold-italic.ttf -------------------------------------------------------------------------------- /resources/qml/uniforms/Sampler2d.qml: -------------------------------------------------------------------------------- 1 | // Auxiliary component 2 | // Instantiated by DynamicPropertyHandler class on C++ side 3 | 4 | import QtQuick 2.12 5 | 6 | Image { 7 | visible: false 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShaderEditor 2 | Simple live Qt/Qml editor for fragment and vertex shaders 3 | 4 | ![preview](https://user-images.githubusercontent.com/11709001/129477943-70273092-9ab3-4217-b1a0-e9e020daf80a.png) 5 | -------------------------------------------------------------------------------- /resources/qml/uniforms/FloatUniform.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | UniformSpinBox { 4 | id: root 5 | 6 | property real uniformValue: 0.0 7 | 8 | onUniformValueChanged: { 9 | value = root.uniformValue * 100 10 | } 11 | 12 | onValueModified: { 13 | root.uniformValue = value / 100.0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /resources/qml/ShaderTextAreaHeader.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | Rectangle { 4 | property alias text: textItem.text 5 | 6 | implicitWidth: 200 7 | implicitHeight: 33 8 | color: "#33352f" 9 | 10 | Text { 11 | id: textItem 12 | 13 | anchors { 14 | verticalCenter: parent.verticalCenter 15 | left: parent.left 16 | leftMargin: 15 17 | } 18 | color: "white" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /resources/qml/effects/ShadowEffect.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtGraphicalEffects 1.12 3 | 4 | DropShadow { 5 | id: root 6 | 7 | property color shadowColor: "black" 8 | property real alpha: 0.35 9 | 10 | horizontalOffset: 0 11 | verticalOffset: 6 12 | radius: 12 13 | samples: 2 * radius + 1 14 | cached: true 15 | color: Qt.rgba(root.shadowColor.r, 16 | root.shadowColor.g, 17 | root.shadowColor.b, 18 | root.alpha) 19 | } 20 | -------------------------------------------------------------------------------- /Dictionary.h: -------------------------------------------------------------------------------- 1 | #ifndef DICTIONARY_H 2 | #define DICTIONARY_H 3 | 4 | #include 5 | #include 6 | 7 | class Dictionary 8 | { 9 | public: 10 | Dictionary(); 11 | 12 | static QSet &conditionals(); 13 | static QSet &statements(); 14 | static QSet &repeats(); 15 | static QSet &types(); 16 | static QSet &starageClasses(); 17 | static QSet &functions(); 18 | static QSet &states(); 19 | static QSet &uniforms(); 20 | }; 21 | 22 | #endif // DICTIONARY_H 23 | -------------------------------------------------------------------------------- /resources/qml/uniforms/Vector2dUniform.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | Column { 4 | id: root 5 | 6 | property vector2d uniformValue: Qt.vector2d(0.0, 0.0) 7 | 8 | spacing: 5 9 | 10 | onUniformValueChanged: { 11 | spinX.value = root.uniformValue.x * 100 12 | spinY.value = root.uniformValue.y * 100 13 | } 14 | 15 | UniformSpinBox { 16 | id: spinX 17 | 18 | width: parent.width 19 | 20 | onValueModified: { 21 | root.uniformValue.x = value / 100.0 22 | } 23 | } 24 | 25 | UniformSpinBox { 26 | id: spinY 27 | 28 | width: parent.width 29 | 30 | onValueModified: { 31 | root.uniformValue.y = value / 100.0 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | *.slo 3 | *.lo 4 | *.o 5 | *.a 6 | *.la 7 | *.lai 8 | *.so 9 | *.so.* 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | object_script.*.Release 15 | object_script.*.Debug 16 | *_plugin_import.cpp 17 | /.qmake.cache 18 | /.qmake.stash 19 | *.pro.user 20 | *.pro.user.* 21 | *.qbs.user 22 | *.qbs.user.* 23 | *.moc 24 | moc_*.cpp 25 | moc_*.h 26 | qrc_*.cpp 27 | ui_*.h 28 | *.qmlc 29 | *.jsc 30 | Makefile* 31 | *build-* 32 | *.qm 33 | *.prl 34 | 35 | # Qt unit tests 36 | target_wrapper.* 37 | 38 | # QtCreator 39 | *.autosave 40 | 41 | # QtCreator Qml 42 | *.qmlproject.user 43 | *.qmlproject.user.* 44 | 45 | # QtCreator CMake 46 | CMakeLists.txt.user* 47 | 48 | # QtCreator 4.8< compilation database 49 | compile_commands.json 50 | 51 | # QtCreator local machine specific files for imported projects 52 | *creator.user* 53 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleExecutable 6 | ShaderEditor 7 | CFBundleIconFile 8 | 9 | CFBundleIdentifier 10 | com.digitalheart.ShaderEditor 11 | CFBundlePackageType 12 | APPL 13 | CFBundleSignature 14 | ???? 15 | LSMinimumSystemVersion 16 | 10.13 17 | NSPrincipalClass 18 | NSApplication 19 | NSSupportsAutomaticGraphicsSwitching 20 | 21 | NSRequiresAquaSystemAppearance 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /resources/qml/LogWindow.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | Rectangle { 4 | id: logWindow 5 | 6 | QtObject { 7 | id: internal 8 | 9 | function statusToValue(valueCompiled, valueUncompiled, valueError) { 10 | switch (shaderOutput.status) { 11 | case ShaderEffect.Compiled: return valueCompiled 12 | case ShaderEffect.Uncompiled: return valueUncompiled 13 | case ShaderEffect.Error: 14 | default: return valueError 15 | } 16 | } 17 | } 18 | 19 | height: Math.min(logTextEdit.height, 300) 20 | color: internal.statusToValue("green", "orange", "red") 21 | 22 | TextEdit { 23 | id: logTextEdit 24 | 25 | text: !!shaderOutput.log ? shaderOutput.log : internal.statusToValue("Compiled", "Compiling...", "Unknown compilation error") 26 | readOnly: true 27 | padding: 5 28 | selectByMouse: !!shaderOutput.log 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /resources/qml/uniforms/Vector3dUniform.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | Column { 4 | id: root 5 | 6 | property vector3d uniformValue: Qt.vector3d(0.0, 0.0, 0.0) 7 | 8 | spacing: 5 9 | 10 | onUniformValueChanged: { 11 | spinX.value = root.uniformValue.x * 100 12 | spinY.value = root.uniformValue.y * 100 13 | spinZ.value = root.uniformValue.z * 100 14 | } 15 | 16 | UniformSpinBox { 17 | id: spinX 18 | 19 | width: parent.width 20 | 21 | onValueModified: { 22 | root.uniformValue.x = value / 100.0 23 | } 24 | } 25 | 26 | UniformSpinBox { 27 | id: spinY 28 | 29 | width: parent.width 30 | 31 | onValueModified: { 32 | root.uniformValue.y = value / 100.0 33 | } 34 | } 35 | 36 | UniformSpinBox { 37 | id: spinZ 38 | 39 | width: parent.width 40 | 41 | onValueModified: { 42 | root.uniformValue.z = value / 100.0 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /resources/qml/uniforms/Sampler2dUniform.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtGraphicalEffects 1.12 3 | 4 | Column { 5 | id: root 6 | 7 | property string uniformValue: "" 8 | 9 | spacing: 5 10 | 11 | UniformButton { 12 | width: parent.width 13 | 14 | text: "Open image" 15 | 16 | onClicked: { 17 | dialogs.openImageDialog.openDialog(function(source) { 18 | root.uniformValue = source 19 | }) 20 | } 21 | } 22 | 23 | Image { 24 | id: preview 25 | 26 | width: parent.width 27 | height: status === Image.Ready ? 135 : 0 28 | source: root.uniformValue 29 | fillMode: Image.PreserveAspectCrop 30 | 31 | layer { 32 | enabled: true 33 | effect: OpacityMask { 34 | maskSource: Rectangle { 35 | width: preview.width 36 | height: preview.height 37 | radius: 5 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Dmitry Lisin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /resources/qml/uniforms/Vector4dUniform.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | Column { 4 | id: root 5 | 6 | property vector4d uniformValue: Qt.vector4d(0.0, 0.0, 0.0, 0.0) 7 | 8 | spacing: 5 9 | 10 | onUniformValueChanged: { 11 | spinX.value = root.uniformValue.x * 100 12 | spinY.value = root.uniformValue.y * 100 13 | spinZ.value = root.uniformValue.z * 100 14 | spinW.value = root.uniformValue.w * 100 15 | } 16 | 17 | UniformSpinBox { 18 | id: spinX 19 | 20 | width: parent.width 21 | 22 | onValueModified: { 23 | root.uniformValue.x = value / 100.0 24 | } 25 | } 26 | 27 | UniformSpinBox { 28 | id: spinY 29 | 30 | width: parent.width 31 | 32 | onValueModified: { 33 | root.uniformValue.y = value / 100.0 34 | } 35 | } 36 | 37 | UniformSpinBox { 38 | id: spinZ 39 | 40 | width: parent.width 41 | 42 | onValueModified: { 43 | root.uniformValue.z = value / 100.0 44 | } 45 | } 46 | 47 | UniformSpinBox { 48 | id: spinW 49 | 50 | width: parent.width 51 | 52 | onValueModified: { 53 | root.uniformValue.w = value / 100.0 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ShaderEditor.pro: -------------------------------------------------------------------------------- 1 | QT += quick quickcontrols2 2 | 3 | CONFIG += c++11 4 | 5 | # The following define makes your compiler emit warnings if you use 6 | # any Qt feature that has been marked deprecated (the exact warnings 7 | # depend on your compiler). Refer to the documentation for the 8 | # deprecated API to know how to port your code away from it. 9 | DEFINES += QT_DEPRECATED_WARNINGS 10 | 11 | # You can also make your code fail to compile if it uses deprecated APIs. 12 | # In order to do so, uncomment the following line. 13 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 14 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 15 | 16 | SOURCES += \ 17 | Dictionary.cpp \ 18 | main.cpp \ 19 | GlslHighlighter.cpp \ 20 | DynamicPropertyHandler.cpp \ 21 | DynamicPropertyModel.cpp 22 | 23 | HEADERS += \ 24 | Dictionary.h \ 25 | GlslHighlighter.h \ 26 | DynamicPropertyHandler.h \ 27 | DynamicPropertyModel.h 28 | 29 | RESOURCES += qml.qrc 30 | 31 | # Additional import path used to resolve QML modules in Qt Creator's code model 32 | QML_IMPORT_PATH = 33 | 34 | # Additional import path used to resolve QML modules just for Qt Quick Designer 35 | QML_DESIGNER_IMPORT_PATH = 36 | 37 | # Additional stuff for mac 38 | macos: QMAKE_INFO_PLIST = Info.plist 39 | 40 | # Default rules for deployment. 41 | qnx: target.path = /tmp/$${TARGET}/bin 42 | else: unix:!android: target.path = /opt/$${TARGET}/bin 43 | !isEmpty(target.path): INSTALLS += target 44 | -------------------------------------------------------------------------------- /GlslHighlighter.h: -------------------------------------------------------------------------------- 1 | #ifndef GLSL_HIGHLIGHTER 2 | #define GLSL_HIGHLIGHTER 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class GlslHighlighter: public QSyntaxHighlighter 9 | { 10 | Q_OBJECT 11 | Q_PROPERTY(QQuickTextDocument* quickTextDocument READ quickTextDocument WRITE setQuickTextDocument NOTIFY quickTextDocumentChanged) 12 | 13 | public: 14 | GlslHighlighter(QTextDocument *parent = nullptr); 15 | 16 | QQuickTextDocument *quickTextDocument() const; 17 | void setQuickTextDocument(QQuickTextDocument *quickTextDocument); 18 | 19 | signals: 20 | void quickTextDocumentChanged(QQuickTextDocument* quickTextDocument); 21 | 22 | protected: 23 | void highlightBlock(const QString &text); 24 | 25 | private: 26 | void addPatternFromSet(QSet &set, QTextCharFormat &format); 27 | 28 | struct HighlightingRule 29 | { 30 | QRegExp pattern; 31 | QTextCharFormat format; 32 | }; 33 | 34 | QVector highlightingRules; 35 | 36 | QRegExp commentStartExpression; 37 | QRegExp commentEndExpression; 38 | 39 | QTextCharFormat statementFormat; 40 | QTextCharFormat commentFormat; 41 | QTextCharFormat preprocessorFormat; 42 | QTextCharFormat numberFormat; 43 | QTextCharFormat typesFormat; 44 | QTextCharFormat keywordFormat; 45 | QTextCharFormat uniformsFormat; 46 | QTextCharFormat swizzleFormat; 47 | 48 | QQuickTextDocument* m_quickTextDocument; 49 | }; 50 | 51 | #endif // GLSL_HIGHLIGHTER 52 | -------------------------------------------------------------------------------- /resources/qml/FloatingMenu.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | Column { 4 | QtObject { 5 | id: internal 6 | 7 | function fillModeText() { 8 | const prefix = "Fill Mode:\n" 9 | 10 | switch (sourceImage.fillMode) { 11 | case Image.Stretch: return prefix + "Stretch" 12 | case Image.PreserveAspectFit: return prefix + "Fit" 13 | case Image.PreserveAspectCrop: return prefix + "Crop" 14 | case Image.Tile: return prefix + "Tile" 15 | case Image.TileVertically: return prefix + "TileV" 16 | case Image.TileHorizontally: return prefix + "TileH" 17 | case Image.Pad: return prefix + "Pad" 18 | default: return prefix + "unknown" 19 | } 20 | } 21 | 22 | function switchFillMode() { 23 | 24 | let currentFillMode = sourceImage.fillMode 25 | if (currentFillMode++ === 6) { 26 | currentFillMode = 0 27 | } 28 | sourceImage.fillMode = currentFillMode 29 | } 30 | 31 | property var menuModel: [ 32 | [ "Custom\nUniforms", customUniforms.toggleVisible ], 33 | [ "Open\nImage", function() { dialogs.openImageDialog.openDialog(function(source) { shaderOutput.source = source }) } ], 34 | [ fillModeText(), switchFillMode ] 35 | ] 36 | } 37 | 38 | spacing: 15 39 | 40 | Repeater { 41 | model: internal.menuModel 42 | delegate: FloatingButton { 43 | text: modelData[0] 44 | 45 | onClicked: { 46 | internal.menuModel[index][1]() // due Qml problems need to call function like that 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "GlslHighlighter.h" 7 | #include "DynamicPropertyHandler.h" 8 | 9 | int main(int argc, char *argv[]) 10 | { 11 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 12 | QGuiApplication app(argc, argv); 13 | 14 | app.setOrganizationName("DigitalMankind"); 15 | app.setOrganizationDomain("DigitalMankind.com"); 16 | 17 | QSurfaceFormat format; 18 | format.setSamples(8); 19 | QSurfaceFormat::setDefaultFormat(format); 20 | 21 | // fonts loading 22 | QFontDatabase::addApplicationFont(":/resources/assets/fonts/consolas-regular.TTF"); 23 | QFontDatabase::addApplicationFont(":/resources/assets/fonts/consolas-bold.TTF"); 24 | QFontDatabase::addApplicationFont(":/resources/assets/fonts/consolas-italic.ttf"); 25 | QFontDatabase::addApplicationFont(":/resources/assets/fonts/consolas-bold-italic.ttf"); 26 | 27 | // types registration 28 | qmlRegisterType("com.dln.Highlighter", 1, 0, "GlslHighlighter"); 29 | qmlRegisterUncreatableType("com.dln.PropertyHandler", 1, 0, "PropertyHandler", "c++ enums"); 30 | 31 | // objects creation 32 | DynamicPropertyHandler dynamicPropertyHandler; 33 | QQmlApplicationEngine engine; // create objects before the engine! 34 | 35 | // setup objects 36 | dynamicPropertyHandler.setEngine(&engine); 37 | 38 | // context properties setting 39 | engine.rootContext()->setContextProperty("_dynamicPropertyHandler", &dynamicPropertyHandler); 40 | 41 | // load main qml file 42 | engine.load(QUrl(QStringLiteral("qrc:/resources/qml/main.qml"))); 43 | 44 | return app.exec(); 45 | } 46 | -------------------------------------------------------------------------------- /resources/qml/uniforms/UniformButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Templates 2.12 as T 3 | 4 | T.Button { 5 | id: root 6 | 7 | QtObject { 8 | id: internal 9 | 10 | property color bgColor 11 | } 12 | 13 | implicitWidth: 30 14 | implicitHeight: 30 15 | 16 | background: Rectangle { 17 | color: internal.bgColor 18 | radius: 5 19 | opacity: root.enabled ? 1.0 : 0.5 20 | } 21 | 22 | contentItem: Item { 23 | width: Math.max(textItem.width + 30, 90) 24 | opacity: root.enabled ? 1.0 : 0.4 25 | 26 | Text { 27 | id: textItem 28 | 29 | anchors { 30 | centerIn: parent 31 | } 32 | text: root.text 33 | horizontalAlignment: Text.AlignHCenter 34 | verticalAlignment: Text.AlignVCenter 35 | color: "black" 36 | } 37 | 38 | Image { 39 | anchors.centerIn: parent 40 | visible: status === Image.Ready && !textItem.text 41 | source: root.icon.source 42 | } 43 | } 44 | 45 | states: [ 46 | State { 47 | name: "normal" 48 | when: !root.down && !root.hovered && !root.highlighted 49 | 50 | PropertyChanges { target: internal; bgColor: "#a8a8a1" } 51 | }, 52 | State { 53 | name: "normalHovered" 54 | when: !root.down && root.hovered && !root.highlighted 55 | 56 | PropertyChanges { target: internal; bgColor: "#8b8b85" } 57 | }, 58 | State { 59 | name: "normalHoveredDown" 60 | when: root.down && root.hovered && !root.highlighted 61 | 62 | PropertyChanges { target: internal; bgColor: "#a8a8a1" } 63 | } 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /DynamicPropertyModel.h: -------------------------------------------------------------------------------- 1 | #ifndef DYNAMICPROPERTYMODEL_H 2 | #define DYNAMICPROPERTYMODEL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct Property { 9 | Property(const QString& name) : m_name(name) {} 10 | Property(const QString& name, int type, const QVariant &value, QQuickItem *object = nullptr) : m_name(name), m_type(type), m_value(value), m_object(object) {} 11 | 12 | bool operator== (const Property& other) const 13 | { 14 | return this->m_name == other.m_name; 15 | } 16 | 17 | QString m_name; 18 | int m_type; 19 | QVariant m_value; 20 | QQuickItem *m_object; // nullptr for simple copyable types 21 | }; 22 | 23 | class DynamicPropertyModel : public QAbstractListModel 24 | { 25 | Q_OBJECT 26 | 27 | public: 28 | enum PropertyRoles { 29 | NameRole = Qt::UserRole + 1, 30 | TypeRole, 31 | ValueRole 32 | }; 33 | Q_ENUM(PropertyRoles) 34 | 35 | explicit DynamicPropertyModel(QObject *parent = nullptr); 36 | 37 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; 38 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; 39 | bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; 40 | QHash roleNames() const override; 41 | 42 | bool prepend(const QString& name, int type, const QVariant &value, QQuickItem *object = nullptr); 43 | bool remove(const QString& name); 44 | bool update(const QString& name, const QVariant &value); 45 | 46 | QQuickItem *object(const QString& name) const; 47 | bool contains(const QString& name) const; 48 | 49 | protected: 50 | QList m_propertyList; 51 | 52 | }; 53 | 54 | #endif // DYNAMICPROPERTYMODEL_H 55 | -------------------------------------------------------------------------------- /DynamicPropertyHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef DYNAMICPROPERTYHANDLER_H 2 | #define DYNAMICPROPERTYHANDLER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "DynamicPropertyModel.h" 9 | 10 | class DynamicPropertyHandler : public QObject 11 | { 12 | Q_OBJECT 13 | Q_PROPERTY(DynamicPropertyModel* dynamicPropertyModel READ dynamicPropertyModel CONSTANT) 14 | 15 | public: 16 | enum PropertyTypes { 17 | Unknown = -1, 18 | Float = 0, 19 | Sampler2d, 20 | Vector2d, 21 | Vector3d, 22 | Vector4d, 23 | Matrix4x4 24 | }; 25 | Q_ENUM(PropertyTypes) 26 | 27 | enum PropertyNameErrorCode { 28 | Valid = 0, 29 | Empty, 30 | FirstCharError, 31 | ReservedKeyword, 32 | ReservedName, 33 | NameCollision, 34 | Invalid 35 | }; 36 | Q_ENUM(PropertyNameErrorCode) 37 | 38 | explicit DynamicPropertyHandler(QObject *parent = nullptr); 39 | 40 | DynamicPropertyModel *dynamicPropertyModel() const; 41 | 42 | void setEngine(QQmlApplicationEngine *engine); 43 | Q_INVOKABLE void registerShaderObject(QQuickItem *object); 44 | 45 | Q_INVOKABLE bool assignProperty(const QString& name, int type, const QVariant &value); 46 | Q_INVOKABLE bool removeProperty(const QString& name); 47 | Q_INVOKABLE bool updateProperty(const QString& name, const QVariant &value); 48 | 49 | Q_INVOKABLE PropertyNameErrorCode nameErrorCode(const QString& name); 50 | Q_INVOKABLE QString humanReadableNameErrorCode(PropertyNameErrorCode nameErrorCode); 51 | 52 | signals: 53 | void shaderObjectChanged(QQuickItem* shaderObject); 54 | 55 | private: 56 | QQuickItem *createSampler2dObject(const QVariant &value); 57 | 58 | protected: 59 | QQmlApplicationEngine *m_engine; 60 | DynamicPropertyModel *m_dynamicPropertyModel; 61 | QQuickItem *m_shaderObject; 62 | }; 63 | 64 | #endif // DYNAMICPROPERTYHANDLER_H 65 | -------------------------------------------------------------------------------- /resources/qml/CustomUniforms.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.12 3 | 4 | import com.dln.PropertyHandler 1.0 5 | 6 | import "uniforms" 7 | 8 | Rectangle { 9 | function toggleVisible() { 10 | if (width === 320) { 11 | width = 0 12 | } 13 | else { 14 | width = 320 15 | } 16 | } 17 | 18 | width: 0 19 | visible: width > 0 20 | color: "#272822" 21 | clip: true 22 | 23 | Behavior on width { NumberAnimation { easing.type: Easing.OutBack; duration: 300 } } 24 | 25 | ListView { 26 | anchors { 27 | fill: parent 28 | margins: 5 29 | } 30 | spacing: 5 31 | model: _dynamicPropertyHandler.dynamicPropertyModel 32 | 33 | header: Item { 34 | width: ListView.view.width 35 | height: headerDelegate.height + ListView.view.spacing 36 | 37 | Uniform { 38 | id: headerDelegate 39 | 40 | width: parent.width 41 | actionIcon: "qrc:/resources/assets/images/add.png" 42 | 43 | onActionClicked: { 44 | if (_dynamicPropertyHandler.assignProperty(name, type, value)) { 45 | headerDelegate.reset() 46 | } 47 | } 48 | } 49 | } 50 | 51 | delegate: Uniform { 52 | width: ListView.view.width 53 | name: NameRole 54 | type: TypeRole 55 | value: ValueRole 56 | actionIcon: "qrc:/resources/assets/images/remove.png" 57 | readOnly: true 58 | 59 | onValueModified: { 60 | _dynamicPropertyHandler.updateProperty(NameRole, value) 61 | } 62 | 63 | onActionClicked: { 64 | dialogs.removeUniformDialog.openDialog(NameRole, function(name) { _dynamicPropertyHandler.removeProperty(name) }) 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /resources/qml/ShaderOutput.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | Item { 4 | id: root 5 | 6 | property alias source: sourceImage.source 7 | property string vertexShader: "" 8 | property string fragmentShader: "" 9 | property int status: 0 10 | property string log: "" 11 | property real time: 0 12 | 13 | Image { 14 | id: sourceImage 15 | 16 | anchors.fill: parent 17 | source: "qrc:/resources/assets/images/sample_image.jpg" 18 | fillMode: Image.PreserveAspectCrop 19 | layer.enabled: true 20 | layer.effect: ShaderEffect { 21 | id: shaderEffect 22 | 23 | property vector2d u_resolution: Qt.vector2d(root.width, root.height) 24 | property vector2d u_mouse: Qt.vector2d(mouseArea.mouseX / root.width, mouseArea.mouseY / root.height) 25 | property real u_time: root.time 26 | 27 | vertexShader: root.vertexShader 28 | fragmentShader: root.fragmentShader 29 | 30 | mesh: GridMesh { resolution: Qt.size(16, 16) } 31 | 32 | Component.onCompleted: { 33 | _dynamicPropertyHandler.registerShaderObject(shaderEffect) 34 | 35 | root.status = status 36 | root.log = log 37 | } 38 | 39 | onStatusChanged: { 40 | root.status = status 41 | } 42 | 43 | onLogChanged: { 44 | root.log = log 45 | } 46 | } 47 | } 48 | 49 | MouseArea { 50 | id: mouseArea 51 | 52 | anchors.fill: parent 53 | hoverEnabled: true 54 | } 55 | 56 | Timer { 57 | running: true 58 | repeat: true 59 | interval: 34 60 | 61 | onTriggered: { 62 | root.time += 0.01 63 | } 64 | } 65 | 66 | FloatingMenu { 67 | anchors { 68 | right: parent.right 69 | bottom: parent.bottom 70 | margins: 20 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /resources/qml/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Window 2.12 3 | import QtQuick.Controls 1.4 4 | 5 | Window { 6 | visible: true 7 | width: 1920 8 | height: 1080 9 | title: qsTr("Shader Editor") 10 | color: "#272822" 11 | 12 | TopMenu { 13 | id: topMenu 14 | 15 | width: parent.width 16 | z: 1 17 | } 18 | 19 | SplitView { 20 | anchors { 21 | left: parent.left 22 | right: customUniforms.left 23 | top: topMenu.bottom 24 | bottom: logWindow.top 25 | } 26 | orientation: Qt.Horizontal 27 | 28 | SplitView { 29 | width: parent.width * 0.4 30 | orientation: Qt.Vertical 31 | 32 | ShaderTextArea { 33 | id: fragmentShaderTextArea 34 | 35 | height: parent.height * 0.5 36 | text: Shaders.emptyShader 37 | title: "Fragment Shader" 38 | } 39 | 40 | ShaderTextArea { 41 | id: vertexShaderTextArea 42 | 43 | height: parent.height * 0.5 44 | text: Shaders.defaultVertexShader 45 | title: "Vertex Shader" 46 | } 47 | } 48 | 49 | ShaderOutput { 50 | id: shaderOutput 51 | 52 | width: parent.width * 0.6 53 | fragmentShader: fragmentShaderTextArea.text 54 | vertexShader: vertexShaderTextArea.text 55 | } 56 | } 57 | 58 | CustomUniforms { 59 | id: customUniforms 60 | 61 | anchors { 62 | right: parent.right 63 | top: topMenu.bottom 64 | bottom: logWindow.top 65 | } 66 | } 67 | 68 | LogWindow { 69 | id: logWindow 70 | 71 | anchors { 72 | left: parent.left 73 | right: parent.right 74 | bottom: parent.bottom 75 | } 76 | } 77 | 78 | Dialogs { 79 | id: dialogs 80 | 81 | anchors.fill: parent 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | resources/assets/fonts/consolas-bold-italic.ttf 4 | resources/assets/fonts/consolas-bold.TTF 5 | resources/assets/fonts/consolas-italic.ttf 6 | resources/assets/fonts/consolas-regular.TTF 7 | resources/assets/images/sample_image.jpg 8 | resources/qml/ShaderTextAreaHeader.qml 9 | resources/qml/LogWindow.qml 10 | resources/qml/main.qml 11 | resources/qml/qmldir 12 | resources/qml/ShaderOutput.qml 13 | resources/qml/Shaders.qml 14 | resources/qml/ShaderTextArea.qml 15 | resources/qml/TopMenu.qml 16 | resources/qml/TopMenuButton.qml 17 | resources/qml/TopMenuButton2.qml 18 | resources/qml/FloatingButton.qml 19 | resources/qml/effects/ShadowEffect.qml 20 | resources/qml/FloatingMenu.qml 21 | resources/qml/CustomUniforms.qml 22 | resources/qml/Dialogs.qml 23 | resources/qml/uniforms/Uniform.qml 24 | resources/qml/uniforms/FloatUniform.qml 25 | resources/qml/uniforms/Vector2dUniform.qml 26 | resources/qml/uniforms/Vector3dUniform.qml 27 | resources/qml/uniforms/Vector4dUniform.qml 28 | resources/qml/uniforms/Sampler2dUniform.qml 29 | resources/qml/uniforms/Matrix4x4Uniform.qml 30 | resources/qml/uniforms/UniformSpinBox.qml 31 | resources/qml/uniforms/Sampler2d.qml 32 | resources/qml/uniforms/UniformButton.qml 33 | resources/assets/images/up_arrow.png 34 | resources/assets/images/down_arrow.png 35 | resources/assets/images/remove.png 36 | resources/assets/images/add.png 37 | 38 | 39 | -------------------------------------------------------------------------------- /resources/qml/uniforms/UniformSpinBox.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Templates 2.12 as T 3 | 4 | T.SpinBox { 5 | id: root 6 | 7 | implicitWidth: contentItem.implicitWidth + up.implicitIndicatorWidth 8 | implicitHeight: 30 9 | rightPadding: up.indicator ? up.indicator.width : 0 10 | 11 | from: -5000 12 | to: 5000 13 | stepSize: 10 14 | 15 | editable: true 16 | validator: DoubleValidator { bottom: root.from; top: root.to } 17 | textFromValue: function(value, locale) { return Number(value / 100.0).toLocaleString(locale, 'f', 2) } 18 | valueFromText: function(text, locale) { return Number.fromLocaleString(locale, text) * 100 } 19 | 20 | contentItem: TextInput { 21 | text: root.displayText 22 | opacity: root.enabled ? 1 : 0.3 23 | leftPadding: 10 24 | font: root.font 25 | selectionColor: "dimgrey" 26 | verticalAlignment: Qt.AlignVCenter 27 | 28 | readOnly: !root.editable 29 | validator: root.validator 30 | inputMethodHints: root.inputMethodHints 31 | selectByMouse: true 32 | clip: true 33 | } 34 | 35 | up.indicator: Image { 36 | x: parent.width - width 37 | width: 30 38 | height: parent.height * 0.5 39 | opacity: up.pressed ? 0.5 : 1.0 40 | source: "qrc:/resources/assets/images/up_arrow.png" 41 | } 42 | 43 | up.onPressedChanged: root.forceActiveFocus() 44 | 45 | down.indicator: Image { 46 | x: parent.width - width 47 | y: parent.height * 0.5 48 | width: 30 49 | height: parent.height * 0.5 50 | opacity: down.pressed ? 0.5 : 1.0 51 | source: "qrc:/resources/assets/images/down_arrow.png" 52 | } 53 | 54 | down.onPressedChanged: root.forceActiveFocus() 55 | 56 | background: Rectangle { 57 | radius: 5 58 | color: root.activeFocus ? "#a8a8a1" : "#8b8b85" 59 | } 60 | 61 | MouseArea { 62 | anchors.fill: parent 63 | acceptedButtons: Qt.NoButton 64 | 65 | onWheel: { 66 | if (wheel.angleDelta.y > 0) { 67 | root.increase() 68 | } 69 | else { 70 | root.decrease() 71 | } 72 | root.valueModified() 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /resources/qml/Dialogs.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | import QtQuick.Controls 2.12 as Controls2 4 | import QtQuick.Dialogs 1.2 as Dialogs1 5 | 6 | Item { 7 | property Controls2.Dialog shaderChangeDialog: Controls2.Dialog { 8 | property int index 9 | property string fragmentShader 10 | property var acceptAction // params: index, fragmentShader 11 | 12 | function openDialog(index, fragmentShader, acceptAction) { 13 | shaderChangeDialog.index = index 14 | shaderChangeDialog.fragmentShader = fragmentShader 15 | shaderChangeDialog.acceptAction = acceptAction 16 | open() 17 | } 18 | 19 | anchors.centerIn: parent 20 | title: "This action will reset your progress, are you sure?" 21 | standardButtons: Controls2.Dialog.Ok | Controls2.Dialog.Cancel 22 | modal: true 23 | 24 | onAccepted: { 25 | shaderChangeDialog.acceptAction(shaderChangeDialog.index, shaderChangeDialog.fragmentShader) 26 | } 27 | } 28 | 29 | property Dialogs1.FileDialog openImageDialog: Dialogs1.FileDialog { 30 | property var acceptAction // params: source 31 | 32 | function openDialog(acceptAction) { 33 | openImageDialog.acceptAction = acceptAction 34 | open() 35 | } 36 | 37 | title: "Please choose a picture" 38 | folder: shortcuts.pictures 39 | nameFilters: [ "Image files (*.jpg *.jpeg *.png)" ] 40 | 41 | onAccepted: { 42 | const files = fileUrls 43 | if (files.length > 0) { 44 | openImageDialog.acceptAction(files[0]) 45 | } 46 | } 47 | } 48 | 49 | property Controls2.Dialog removeUniformDialog: Controls2.Dialog { 50 | property string name 51 | property var acceptAction // params: name 52 | 53 | function openDialog(name, acceptAction) { 54 | removeUniformDialog.name = name 55 | removeUniformDialog.acceptAction = acceptAction 56 | open() 57 | } 58 | 59 | anchors.centerIn: parent 60 | title: "Do you really want to remove this uniform?" 61 | standardButtons: Controls2.Dialog.Ok | Controls2.Dialog.Cancel 62 | modal: true 63 | 64 | onAccepted: { 65 | removeUniformDialog.acceptAction(removeUniformDialog.name) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /resources/qml/FloatingButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Templates 2.12 as T 3 | 4 | import "effects" 5 | 6 | T.Button { 7 | id: root 8 | 9 | QtObject { 10 | id: internal 11 | 12 | property real btnScale 13 | property color bgColor 14 | 15 | Behavior on btnScale { NumberAnimation { easing.type: Easing.OutBack; duration: 300 } } 16 | } 17 | 18 | implicitWidth: contentItem.width 19 | implicitHeight: 55 20 | 21 | background: Rectangle { 22 | scale: internal.btnScale 23 | color: internal.bgColor 24 | radius: 9 25 | layer { 26 | enabled: true 27 | effect: ShadowEffect {} 28 | } 29 | } 30 | 31 | contentItem: Item { 32 | width: Math.max(textItem.width + 30, 90) 33 | scale: internal.btnScale 34 | 35 | Text { 36 | id: textItem 37 | 38 | anchors.centerIn: parent 39 | text: root.text 40 | horizontalAlignment: Text.AlignHCenter 41 | verticalAlignment: Text.AlignVCenter 42 | color: "black" 43 | } 44 | } 45 | 46 | states: [ 47 | State { 48 | name: "normal" 49 | when: !root.down && !root.hovered && !root.highlighted 50 | 51 | PropertyChanges { target: internal; btnScale: 1.0; bgColor: "#a8a8a1" } 52 | }, 53 | State { 54 | name: "normalHovered" 55 | when: !root.down && root.hovered && !root.highlighted 56 | 57 | PropertyChanges { target: internal; btnScale: 1.07; bgColor: "#8b8b85" } 58 | }, 59 | State { 60 | name: "normalHoveredDown" 61 | when: root.down && root.hovered && !root.highlighted 62 | 63 | PropertyChanges { target: internal; btnScale: 1.0; bgColor: "#8b8b85" } 64 | }, 65 | State { 66 | name: "highlighted" 67 | when: !root.down && !root.hovered && root.highlighted 68 | 69 | PropertyChanges { target: internal; btnScale: 1.07; bgColor: "#a8a8a1" } 70 | }, 71 | State { 72 | name: "highlightedHovered" 73 | when: !root.down && root.hovered && root.highlighted 74 | 75 | PropertyChanges { target: internal; btnScale: 1.14; bgColor: "#8b8b85" } 76 | }, 77 | State { 78 | name: "highlightedHoveredDown" 79 | when: root.down && root.hovered && root.highlighted 80 | 81 | PropertyChanges { target: internal; btnScale: 1.07; bgColor: "#8b8b85" } 82 | } 83 | ] 84 | } 85 | -------------------------------------------------------------------------------- /resources/qml/ShaderTextArea.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.12 3 | 4 | import com.dln.Highlighter 1.0 5 | 6 | Item { 7 | property alias text: textArea.text 8 | property alias title: header.text 9 | 10 | clip: true 11 | 12 | ShaderTextAreaHeader { 13 | id: header 14 | 15 | width: parent.width 16 | z: 1 17 | } 18 | 19 | Flickable { 20 | id: scrollArea 21 | 22 | anchors { 23 | fill: parent 24 | topMargin: header.height 25 | leftMargin: 40 26 | } 27 | leftMargin: 10 28 | rightMargin: 10 29 | topMargin: 10 30 | bottomMargin: 10 31 | boundsBehavior: Flickable.StopAtBounds 32 | 33 | ScrollBar.vertical: ScrollBar {} 34 | ScrollBar.horizontal: ScrollBar {} 35 | 36 | TextArea.flickable: TextArea { 37 | id: textArea 38 | 39 | padding: 0 40 | selectByMouse: true 41 | cursorVisible: true 42 | persistentSelection: true 43 | wrapMode: TextEdit.NoWrap 44 | background: Rectangle { width: parent.width; height: parent.height; color: "#272822" } 45 | color: "#f8f8de" 46 | selectedTextColor: "#f8f8de" 47 | selectionColor: "#49483e" 48 | font.family: "Consolas" 49 | } 50 | } 51 | 52 | // line numbers column 53 | Rectangle { 54 | width: scrollArea.anchors.leftMargin 55 | height: parent.height 56 | color: "#272822" 57 | 58 | Rectangle { 59 | anchors.horizontalCenter: parent.right 60 | width: 2 61 | height: parent.height 62 | color: "#33352f" 63 | } 64 | 65 | Column { 66 | anchors { 67 | right: parent.right 68 | top: parent.top 69 | rightMargin: 6 70 | topMargin: scrollArea.anchors.topMargin + scrollArea.contentItem.y 71 | } 72 | 73 | Repeater { 74 | model: textArea.lineCount 75 | 76 | Text { 77 | anchors.right: parent.right 78 | height: fontMetrics.visibleHeight 79 | font.family: "Consolas" 80 | text: index + 1 81 | color: "#9d9d96" 82 | } 83 | } 84 | } 85 | } 86 | 87 | // current line highlight 88 | FontMetrics { 89 | id: fontMetrics 90 | 91 | property real visibleHeight: textArea.contentHeight / textArea.lineCount // workaround 92 | 93 | font: textArea.font 94 | } 95 | 96 | Rectangle { 97 | y: scrollArea.anchors.topMargin + scrollArea.contentItem.y + textArea.cursorRectangle.y 98 | height: fontMetrics.visibleHeight 99 | width: parent.width 100 | color: "white" 101 | opacity: 0.027 102 | } 103 | 104 | GlslHighlighter { 105 | quickTextDocument: textArea.textDocument 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /resources/qml/TopMenu.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | ListView { 4 | QtObject { 5 | id: internal 6 | 7 | property int currentShader: 0 8 | property var shaderModel: [ 9 | { text: "Empty", fragmentShader: Shaders.emptyShader }, 10 | { text: "Empty UV", fragmentShader: Shaders.emptyUvShader }, 11 | { text: "Simple", fragmentShader: Shaders.simpleShader }, 12 | { text: "Linear", fragmentShader: Shaders.linearDemoShader }, 13 | { text: "Expo", fragmentShader: Shaders.expoDemoShader }, 14 | { text: "Greyscale", fragmentShader: Shaders.greyscaleShader }, 15 | { text: "Flag", fragmentShader: Shaders.russiaFlagShader }, 16 | { text: "Circle", fragmentShader: Shaders.circleShader }, 17 | { text: "Magnifier Glass", fragmentShader: Shaders.magnifierGlassShader }, 18 | { text: "Magic Lens", fragmentShader: Shaders.magicLensShader }, 19 | { text: "Distance Field", fragmentShader: Shaders.distanceFieldShader }, 20 | { text: "Polar Coordinates 1", fragmentShader: Shaders.polarCoordinatesShader1 }, 21 | { text: "Polar Coordinates 2", fragmentShader: Shaders.polarCoordinatesShader2 }, 22 | { text: "Ripple Effect 1", fragmentShader: Shaders.rippleEffectShader1 }, 23 | { text: "Ripple Effect 2", fragmentShader: Shaders.rippleEffectShader2 }, 24 | { text: "Heat haze air", fragmentShader: Shaders.heatHazeAirShader }, 25 | { text: "Fractal", fragmentShader: Shaders.fractalShader }, 26 | { text: "Complex Fog", fragmentShader: Shaders.complexFogShader } 27 | ] 28 | } 29 | 30 | implicitWidth: 200 31 | implicitHeight: 55 32 | orientation: ListView.Horizontal 33 | spacing: 10 34 | 35 | model: internal.shaderModel 36 | delegate: TopMenuButton2 { 37 | text: modelData.text 38 | highlighted: index === internal.currentShader 39 | z: highlighted ? 2 : 1 40 | 41 | onClicked: { 42 | const originalFragmentShader = internal.shaderModel[internal.currentShader].fragmentShader 43 | const originalVertexShader = Shaders.defaultVertexShader 44 | 45 | if (originalFragmentShader === fragmentShaderTextArea.text && originalVertexShader === vertexShaderTextArea.text) { 46 | internal.currentShader = index 47 | fragmentShaderTextArea.text = modelData.fragmentShader 48 | vertexShaderTextArea.text = Shaders.defaultVertexShader 49 | } 50 | else { 51 | dialogs.shaderChangeDialog.openDialog(index, modelData.fragmentShader, 52 | function(index, fragmentShader) { 53 | internal.currentShader = index 54 | fragmentShaderTextArea.text = fragmentShader 55 | vertexShaderTextArea.text = Shaders.defaultVertexShader 56 | }) 57 | } 58 | } 59 | } 60 | header: Item { width: 10; height: 1 } 61 | footer: Item { width: 10; height: 1 } 62 | } 63 | -------------------------------------------------------------------------------- /resources/qml/TopMenuButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Templates 2.12 as T 3 | import QtQuick.Shapes 1.12 4 | 5 | T.Button { 6 | id: root 7 | 8 | QtObject { 9 | id: internal 10 | 11 | property real xOffset 12 | property real yOffset 13 | property real textOffset 14 | property color bgColor 15 | 16 | Behavior on xOffset { NumberAnimation { duration: 150 } } 17 | Behavior on yOffset { NumberAnimation { duration: 150 } } 18 | Behavior on textOffset { NumberAnimation { duration: 150 } } 19 | } 20 | 21 | implicitWidth: contentItem.width 22 | implicitHeight: 45 23 | 24 | background: Shape { 25 | id: shapeBg 26 | 27 | ShapePath { 28 | strokeWidth: -1 // dirty hack 29 | fillColor: "#272822" 30 | startX: 0; startY: 0 31 | 32 | PathLine { x: shapeBg.width; y: 0 } 33 | PathLine { x: shapeBg.width; y: shapeBg.height } 34 | 35 | PathCubic { 36 | control1X: shapeBg.width - internal.xOffset; control1Y: shapeBg.height + internal.yOffset 37 | control2X: internal.xOffset; control2Y: shapeBg.height + internal.yOffset 38 | x: 0; y: shapeBg.height 39 | } 40 | 41 | PathLine { x: 0; y: 0 } 42 | } 43 | } 44 | 45 | contentItem: Item { 46 | width: Math.max(textItem.width + 20, 90) 47 | 48 | Text { 49 | id: textItem 50 | 51 | anchors { 52 | centerIn: parent 53 | verticalCenterOffset: internal.textOffset 54 | } 55 | text: root.text 56 | horizontalAlignment: Text.AlignHCenter 57 | verticalAlignment: Text.AlignVCenter 58 | color: "white" 59 | } 60 | } 61 | 62 | states: [ 63 | State { 64 | name: "normal" 65 | when: !root.down && !root.hovered && !root.highlighted 66 | 67 | PropertyChanges { target: internal; xOffset: 0; yOffset: 0; textOffset: 0 } 68 | PropertyChanges { target: textItem; color: "white" } 69 | }, 70 | State { 71 | name: "normalHovered" 72 | when: !root.down && root.hovered && !root.highlighted 73 | 74 | PropertyChanges { target: internal; xOffset: 20; yOffset: 10; textOffset: 3 } 75 | PropertyChanges { target: textItem; color: "#58d9ef" } 76 | }, 77 | State { 78 | name: "normalHoveredDown" 79 | when: root.down && root.hovered && !root.highlighted 80 | 81 | PropertyChanges { target: internal; xOffset: 25; yOffset: 15; textOffset: 6 } 82 | PropertyChanges { target: textItem; color: "#58d9ef" } 83 | }, 84 | State { 85 | name: "highlighted" 86 | when: !root.down && !root.hovered && root.highlighted 87 | 88 | PropertyChanges { target: internal; xOffset: 20; yOffset: 10; textOffset: 3 } 89 | PropertyChanges { target: textItem; color: "#58d9ef" } 90 | }, 91 | State { 92 | name: "highlightedHovered" 93 | when: !root.down && root.hovered && root.highlighted 94 | 95 | PropertyChanges { target: internal; xOffset: 25; yOffset: 15; textOffset: 6 } 96 | PropertyChanges { target: textItem; color: "white" } 97 | }, 98 | State { 99 | name: "highlightedHoveredDown" 100 | when: root.down && root.hovered && root.highlighted 101 | 102 | PropertyChanges { target: internal; xOffset: 30; yOffset: 20; textOffset: 9 } 103 | PropertyChanges { target: textItem; color: "white" } 104 | } 105 | ] 106 | } 107 | -------------------------------------------------------------------------------- /DynamicPropertyModel.cpp: -------------------------------------------------------------------------------- 1 | #include "DynamicPropertyModel.h" 2 | 3 | DynamicPropertyModel::DynamicPropertyModel(QObject *parent) : QAbstractListModel(parent) {} 4 | 5 | int DynamicPropertyModel::rowCount(const QModelIndex &parent) const 6 | { 7 | if (parent.isValid()) 8 | { 9 | return 0; 10 | } 11 | 12 | return m_propertyList.count(); 13 | } 14 | 15 | QVariant DynamicPropertyModel::data(const QModelIndex &index, int role) const 16 | { 17 | if (!index.isValid()) 18 | { 19 | return QVariant(); 20 | } 21 | 22 | const Property currentProperty = m_propertyList[index.row()]; 23 | switch (role) 24 | { 25 | case PropertyRoles::NameRole: 26 | return QVariant::fromValue(currentProperty.m_name); 27 | case PropertyRoles::TypeRole: 28 | return QVariant::fromValue(currentProperty.m_type); 29 | case PropertyRoles::ValueRole: 30 | return QVariant::fromValue(currentProperty.m_value); 31 | default: 32 | break; 33 | } 34 | 35 | return QVariant(); 36 | } 37 | 38 | bool DynamicPropertyModel::setData(const QModelIndex &index, const QVariant &value, int role) 39 | { 40 | if (!index.isValid()) 41 | { 42 | return false; 43 | } 44 | 45 | Property& currentProperty = m_propertyList[index.row()]; 46 | switch (role) 47 | { 48 | case PropertyRoles::NameRole: 49 | currentProperty.m_name = value.toString(); 50 | break; 51 | case PropertyRoles::TypeRole: 52 | currentProperty.m_type = value.toInt(); 53 | break; 54 | case PropertyRoles::ValueRole: 55 | currentProperty.m_value = value; 56 | break; 57 | default: 58 | QAbstractListModel::setData(index, value, role); 59 | break; 60 | } 61 | 62 | return true; 63 | } 64 | 65 | QHash DynamicPropertyModel::roleNames() const 66 | { 67 | QHash roles; 68 | roles[PropertyRoles::NameRole] = "NameRole"; 69 | roles[PropertyRoles::TypeRole] = "TypeRole"; 70 | roles[PropertyRoles::ValueRole] = "ValueRole"; 71 | return roles; 72 | } 73 | 74 | bool DynamicPropertyModel::prepend(const QString &name, int type, const QVariant &value, QQuickItem *object) 75 | { 76 | Property newProperty(name, type, value, object); 77 | 78 | if (m_propertyList.contains(newProperty)) 79 | { 80 | return false; 81 | } 82 | 83 | beginInsertRows(QModelIndex(), 0, 0); 84 | m_propertyList.prepend(newProperty); 85 | endInsertRows(); 86 | 87 | return true; 88 | } 89 | 90 | bool DynamicPropertyModel::remove(const QString &name) 91 | { 92 | Property currentProperty(name); 93 | 94 | if (!m_propertyList.contains(currentProperty)) 95 | { 96 | return false; 97 | } 98 | 99 | const int propertyIndex = m_propertyList.indexOf(currentProperty); 100 | 101 | beginRemoveRows(QModelIndex(), propertyIndex, propertyIndex); 102 | m_propertyList.removeAt(propertyIndex); 103 | endRemoveRows(); 104 | 105 | return true; 106 | } 107 | 108 | bool DynamicPropertyModel::update(const QString &name, const QVariant &value) 109 | { 110 | Property currentProperty(name); 111 | 112 | if (!m_propertyList.contains(currentProperty)) 113 | { 114 | return false; 115 | } 116 | 117 | const int propertyIndex = m_propertyList.indexOf(currentProperty); 118 | const QModelIndex modelIndex = index(propertyIndex); 119 | 120 | if (setData(modelIndex, value, PropertyRoles::ValueRole)) 121 | { 122 | emit dataChanged(modelIndex, modelIndex, { PropertyRoles::ValueRole }); 123 | return true; 124 | } 125 | 126 | return false; // we will not reach this line 127 | } 128 | 129 | QQuickItem *DynamicPropertyModel::object(const QString& name) const 130 | { 131 | auto result = std::find(m_propertyList.begin(), m_propertyList.end(), Property(name)); 132 | if (result != m_propertyList.end()) 133 | { 134 | return (*result).m_object; 135 | } 136 | return nullptr; 137 | } 138 | 139 | bool DynamicPropertyModel::contains(const QString &name) const 140 | { 141 | return m_propertyList.contains(Property(name)); 142 | } 143 | -------------------------------------------------------------------------------- /resources/qml/TopMenuButton2.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Templates 2.12 as T 3 | import QtGraphicalEffects 1.12 4 | 5 | import "effects" 6 | 7 | T.Button { 8 | id: root 9 | 10 | QtObject { 11 | id: internal 12 | 13 | property real topOffset 14 | property real btnScale 15 | property color bgColor 16 | property int outlineWidth: 7 17 | 18 | Behavior on topOffset { NumberAnimation { easing.type: Easing.OutBack; duration: 300 } } 19 | Behavior on btnScale { NumberAnimation { easing.type: Easing.OutBack; duration: 300 } } 20 | } 21 | 22 | implicitWidth: contentItem.width 23 | implicitHeight: 55 24 | 25 | background: Item { 26 | scale: internal.btnScale 27 | 28 | Item { 29 | anchors { 30 | horizontalCenter: bgRect.horizontalCenter 31 | bottom: bgRect.bottom 32 | bottomMargin: -internal.outlineWidth 33 | } 34 | width: outlineRect.width 35 | height: outlineRect.height * 0.2 36 | clip: true 37 | 38 | Rectangle { 39 | id: outlineRect 40 | 41 | anchors.bottom: parent.bottom 42 | width: bgRect.width + 2 * internal.outlineWidth 43 | height: bgRect.height + 2 * internal.outlineWidth 44 | color: "#272822" 45 | radius: bgRect.radius + internal.outlineWidth 46 | } 47 | } 48 | 49 | Rectangle { 50 | id: bgRect 51 | 52 | anchors { 53 | centerIn: parent 54 | verticalCenterOffset: internal.topOffset 55 | } 56 | width: parent.width 57 | height: parent.height - 18 58 | radius: 9 59 | color: internal.bgColor 60 | layer { 61 | enabled: true 62 | effect: ShadowEffect {} 63 | } 64 | } 65 | } 66 | 67 | contentItem: Item { 68 | width: Math.max(textItem.width + 30, 90) 69 | scale: internal.btnScale 70 | 71 | Text { 72 | id: textItem 73 | 74 | anchors { 75 | centerIn: parent 76 | verticalCenterOffset: internal.topOffset 77 | } 78 | text: root.text 79 | horizontalAlignment: Text.AlignHCenter 80 | verticalAlignment: Text.AlignVCenter 81 | } 82 | } 83 | 84 | states: [ 85 | State { 86 | name: "normal" 87 | when: !root.down && !root.hovered && !root.highlighted 88 | 89 | PropertyChanges { target: internal; topOffset: 0; btnScale: 1.0; bgColor: "transparent" } 90 | PropertyChanges { target: textItem; color: "white" } 91 | }, 92 | State { 93 | name: "normalHovered" 94 | when: !root.down && root.hovered && !root.highlighted 95 | 96 | PropertyChanges { target: internal; topOffset: 0; btnScale: 1.07; bgColor: "#33352f" } 97 | PropertyChanges { target: textItem; color: "white" } 98 | }, 99 | State { 100 | name: "normalHoveredDown" 101 | when: root.down && root.hovered && !root.highlighted 102 | 103 | PropertyChanges { target: internal; topOffset: 0; btnScale: 1.0; bgColor: "#33352f" } 104 | PropertyChanges { target: textItem; color: "white" } 105 | }, 106 | State { 107 | name: "highlighted" 108 | when: !root.down && !root.hovered && root.highlighted 109 | 110 | PropertyChanges { target: internal; topOffset: 8; btnScale: 1.07; bgColor: "#a8a8a1" } 111 | PropertyChanges { target: textItem; color: "black" } 112 | }, 113 | State { 114 | name: "highlightedHovered" 115 | when: !root.down && root.hovered && root.highlighted 116 | 117 | PropertyChanges { target: internal; topOffset: 8; btnScale: 1.14; bgColor: "#8b8b85" } 118 | PropertyChanges { target: textItem; color: "black" } 119 | }, 120 | State { 121 | name: "highlightedHoveredDown" 122 | when: root.down && root.hovered && root.highlighted 123 | 124 | PropertyChanges { target: internal; topOffset: 8; btnScale: 1.07; bgColor: "#8b8b85" } 125 | PropertyChanges { target: textItem; color: "black" } 126 | } 127 | ] 128 | } 129 | -------------------------------------------------------------------------------- /resources/qml/uniforms/Matrix4x4Uniform.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | Column { 4 | id: root 5 | 6 | property matrix4x4 uniformValue: Qt.matrix4x4(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) 7 | 8 | spacing: 5 9 | 10 | onUniformValueChanged: { 11 | spinM11.value = root.uniformValue.m11 * 100 12 | spinM12.value = root.uniformValue.m12 * 100 13 | spinM13.value = root.uniformValue.m13 * 100 14 | spinM14.value = root.uniformValue.m14 * 100 15 | 16 | spinM21.value = root.uniformValue.m21 * 100 17 | spinM22.value = root.uniformValue.m22 * 100 18 | spinM23.value = root.uniformValue.m23 * 100 19 | spinM24.value = root.uniformValue.m24 * 100 20 | 21 | spinM31.value = root.uniformValue.m31 * 100 22 | spinM32.value = root.uniformValue.m32 * 100 23 | spinM33.value = root.uniformValue.m33 * 100 24 | spinM34.value = root.uniformValue.m34 * 100 25 | 26 | spinM41.value = root.uniformValue.m41 * 100 27 | spinM42.value = root.uniformValue.m42 * 100 28 | spinM43.value = root.uniformValue.m43 * 100 29 | spinM44.value = root.uniformValue.m44 * 100 30 | } 31 | 32 | Row { 33 | width: parent.width 34 | spacing: 5 35 | 36 | UniformSpinBox { 37 | id: spinM11 38 | 39 | width: parent.width * 0.25 - 3.75 40 | 41 | onValueModified: { 42 | root.uniformValue.m11 = value / 100.0 43 | } 44 | } 45 | 46 | UniformSpinBox { 47 | id: spinM12 48 | 49 | width: parent.width * 0.25 - 3.75 50 | 51 | onValueModified: { 52 | root.uniformValue.m12 = value / 100.0 53 | } 54 | } 55 | 56 | UniformSpinBox { 57 | id: spinM13 58 | 59 | width: parent.width * 0.25 - 3.75 60 | 61 | onValueModified: { 62 | root.uniformValue.m13 = value / 100.0 63 | } 64 | } 65 | 66 | UniformSpinBox { 67 | id: spinM14 68 | 69 | width: parent.width * 0.25 - 3.75 70 | 71 | onValueModified: { 72 | root.uniformValue.m14 = value / 100.0 73 | } 74 | } 75 | } 76 | 77 | Row { 78 | width: parent.width 79 | spacing: 5 80 | 81 | UniformSpinBox { 82 | id: spinM21 83 | 84 | width: parent.width * 0.25 - 3.75 85 | 86 | onValueModified: { 87 | root.uniformValue.m21 = value / 100.0 88 | } 89 | } 90 | 91 | UniformSpinBox { 92 | id: spinM22 93 | 94 | width: parent.width * 0.25 - 3.75 95 | 96 | onValueModified: { 97 | root.uniformValue.m22 = value / 100.0 98 | } 99 | } 100 | 101 | UniformSpinBox { 102 | id: spinM23 103 | 104 | width: parent.width * 0.25 - 3.75 105 | 106 | onValueModified: { 107 | root.uniformValue.m23 = value / 100.0 108 | } 109 | } 110 | 111 | UniformSpinBox { 112 | id: spinM24 113 | 114 | width: parent.width * 0.25 - 3.75 115 | 116 | onValueModified: { 117 | root.uniformValue.m24 = value / 100.0 118 | } 119 | } 120 | } 121 | 122 | Row { 123 | width: parent.width 124 | spacing: 5 125 | 126 | UniformSpinBox { 127 | id: spinM31 128 | 129 | width: parent.width * 0.25 - 3.75 130 | 131 | onValueModified: { 132 | root.uniformValue.m31 = value / 100.0 133 | } 134 | } 135 | 136 | UniformSpinBox { 137 | id: spinM32 138 | 139 | width: parent.width * 0.25 - 3.75 140 | 141 | onValueModified: { 142 | root.uniformValue.m32 = value / 100.0 143 | } 144 | } 145 | 146 | UniformSpinBox { 147 | id: spinM33 148 | 149 | width: parent.width * 0.25 - 3.75 150 | 151 | onValueModified: { 152 | root.uniformValue.m33 = value / 100.0 153 | } 154 | } 155 | 156 | UniformSpinBox { 157 | id: spinM34 158 | 159 | width: parent.width * 0.25 - 3.75 160 | 161 | onValueModified: { 162 | root.uniformValue.m34 = value / 100.0 163 | } 164 | } 165 | } 166 | 167 | Row { 168 | width: parent.width 169 | spacing: 5 170 | 171 | UniformSpinBox { 172 | id: spinM41 173 | 174 | width: parent.width * 0.25 - 3.75 175 | 176 | onValueModified: { 177 | root.uniformValue.m41 = value / 100.0 178 | } 179 | } 180 | 181 | UniformSpinBox { 182 | id: spinM42 183 | 184 | width: parent.width * 0.25 - 3.75 185 | 186 | onValueModified: { 187 | root.uniformValue.m42 = value / 100.0 188 | } 189 | } 190 | 191 | UniformSpinBox { 192 | id: spinM43 193 | 194 | width: parent.width * 0.25 - 3.75 195 | 196 | onValueModified: { 197 | root.uniformValue.m43 = value / 100.0 198 | } 199 | } 200 | 201 | UniformSpinBox { 202 | id: spinM44 203 | 204 | width: parent.width * 0.25 - 3.75 205 | 206 | onValueModified: { 207 | root.uniformValue.m44 = value / 100.0 208 | } 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /DynamicPropertyHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "DynamicPropertyHandler.h" 2 | 3 | #include "Dictionary.h" 4 | 5 | DynamicPropertyHandler::DynamicPropertyHandler(QObject *parent) 6 | : QObject(parent) 7 | , m_engine(nullptr) 8 | , m_shaderObject(nullptr) 9 | { 10 | m_dynamicPropertyModel = new DynamicPropertyModel(parent); 11 | } 12 | 13 | DynamicPropertyModel *DynamicPropertyHandler::dynamicPropertyModel() const 14 | { 15 | return m_dynamicPropertyModel; 16 | } 17 | 18 | void DynamicPropertyHandler::setEngine(QQmlApplicationEngine *engine) 19 | { 20 | m_engine = engine; 21 | } 22 | 23 | void DynamicPropertyHandler::registerShaderObject(QQuickItem *object) 24 | { 25 | if (m_shaderObject != object) 26 | { 27 | m_shaderObject = object; 28 | emit shaderObjectChanged(object); 29 | } 30 | } 31 | 32 | bool DynamicPropertyHandler::assignProperty(const QString& name, int type, const QVariant &value) 33 | { 34 | QQuickItem *object = nullptr; 35 | 36 | // create specific sampler2d object if needed 37 | if (type == PropertyTypes::Sampler2d) 38 | { 39 | object = createSampler2dObject(value); 40 | } 41 | 42 | // set property 43 | if (m_shaderObject && m_dynamicPropertyModel->prepend(name, type, value, object)) 44 | { 45 | if (object) 46 | { 47 | m_shaderObject->setProperty(name.toStdString().c_str(), QVariant::fromValue(object)); 48 | } 49 | else 50 | { 51 | m_shaderObject->setProperty(name.toStdString().c_str(), value); 52 | } 53 | 54 | return true; 55 | } 56 | 57 | return false; 58 | } 59 | 60 | bool DynamicPropertyHandler::removeProperty(const QString &name) 61 | { 62 | QQuickItem *object = m_dynamicPropertyModel->object(name); 63 | 64 | if (m_shaderObject && m_dynamicPropertyModel->remove(name)) 65 | { 66 | m_shaderObject->setProperty(name.toStdString().c_str(), QVariant()); 67 | 68 | if (object) // delete the object if it was created before 69 | { 70 | object->deleteLater(); 71 | } 72 | 73 | return true; 74 | } 75 | 76 | return false; 77 | } 78 | 79 | bool DynamicPropertyHandler::updateProperty(const QString &name, const QVariant &value) 80 | { 81 | if (m_shaderObject && m_dynamicPropertyModel->update(name, value)) 82 | { 83 | QQuickItem *object = m_dynamicPropertyModel->object(name); 84 | if (object) // specific sampler2d case 85 | { 86 | object->setProperty("source", value); 87 | } 88 | else 89 | { 90 | m_shaderObject->setProperty(name.toStdString().c_str(), value); 91 | } 92 | 93 | return true; 94 | } 95 | 96 | return false; 97 | } 98 | 99 | DynamicPropertyHandler::PropertyNameErrorCode DynamicPropertyHandler::nameErrorCode(const QString &name) 100 | { 101 | if (name.isNull() || name.isEmpty()) 102 | { 103 | return PropertyNameErrorCode::Empty; 104 | } 105 | 106 | // If first character is invalid 107 | if (!((name[0] >= 'a' && name[0] <= 'z') || (name[0] >= 'A' && name[0] <= 'Z') || name[0] == '_')) 108 | { 109 | return PropertyNameErrorCode::FirstCharError; 110 | } 111 | 112 | // Traverse the string for the rest of the characters 113 | for (int i = 1; i < name.length(); i++) 114 | { 115 | if (!((name[i] >= 'a' && name[i] <= 'z') || (name[i] >= 'A' && name[i] <= 'Z') || (name[i] >= '0' && name[i] <= '9') || name[i] == '_')) 116 | { 117 | return PropertyNameErrorCode::Invalid; 118 | } 119 | } 120 | 121 | // Check for keywords 122 | if (Dictionary::conditionals().contains(name) 123 | || Dictionary::statements().contains(name) 124 | || Dictionary::repeats().contains(name) 125 | || Dictionary::types().contains(name) 126 | || Dictionary::starageClasses().contains(name)) 127 | { 128 | return PropertyNameErrorCode::ReservedKeyword; 129 | } 130 | 131 | // Check for reserved variables 132 | if (Dictionary::functions().contains(name) 133 | || Dictionary::states().contains(name) 134 | || Dictionary::uniforms().contains(name)) 135 | { 136 | return PropertyNameErrorCode::ReservedName; 137 | } 138 | 139 | // Check for existing variables with the same name 140 | if (m_dynamicPropertyModel->contains(name)) 141 | { 142 | return PropertyNameErrorCode::NameCollision; 143 | } 144 | 145 | // String is a valid identifier 146 | return PropertyNameErrorCode::Valid; 147 | } 148 | 149 | QString DynamicPropertyHandler::humanReadableNameErrorCode(DynamicPropertyHandler::PropertyNameErrorCode nameErrorCode) 150 | { 151 | switch (nameErrorCode) 152 | { 153 | case PropertyNameErrorCode::FirstCharError: return "Uniform name can't be started with this character"; 154 | case PropertyNameErrorCode::ReservedKeyword: return "Uniform name matches a reserved keyword"; 155 | case PropertyNameErrorCode::ReservedName: return "Uniform name matches a reserved name"; 156 | case PropertyNameErrorCode::NameCollision: return "Uniform with this name already exists"; 157 | case PropertyNameErrorCode::Invalid: return "Invalid uniform name"; 158 | default: return ""; 159 | } 160 | } 161 | 162 | QQuickItem *DynamicPropertyHandler::createSampler2dObject(const QVariant &value) 163 | { 164 | QQmlComponent component(m_engine, QUrl(QStringLiteral("qrc:/resources/qml/uniforms/Sampler2d.qml"))); 165 | QQuickItem *object = qobject_cast(component.createWithInitialProperties({ { "source", value.toString() } })); 166 | QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership); 167 | object->setParent(m_engine); 168 | 169 | return object; 170 | } 171 | -------------------------------------------------------------------------------- /Dictionary.cpp: -------------------------------------------------------------------------------- 1 | #include "Dictionary.h" 2 | 3 | namespace 4 | { 5 | 6 | QSet CONDITIONALS { "if", "else" }; 7 | 8 | QSet STATEMENTS { "break", "return", "continue", "discard" }; 9 | 10 | QSet REPEATS { "while", "for", "do" }; 11 | 12 | QSet TYPES { 13 | "void" 14 | , "bool" 15 | , "bvec2" 16 | , "bvec3" 17 | , "bvec4" 18 | , "int" 19 | , "ivec2" 20 | , "ivec3" 21 | , "ivec4" 22 | , "uint" 23 | , "uvec2" 24 | , "uvec3" 25 | , "uvec4" 26 | , "float" 27 | , "vec2" 28 | , "vec3" 29 | , "vec4" 30 | , "double" 31 | , "dvec2" 32 | , "dvec3" 33 | , "dvec4" 34 | , "mat2" 35 | , "mat3" 36 | , "mat4" 37 | , "sampler1D" 38 | , "sampler2D" 39 | , "sampler3D" 40 | , "samplerCUBE" 41 | , "sampler1DShadow" 42 | , "sampler2DShadow" 43 | , "struct" 44 | }; 45 | 46 | QSet STORAGE_CLASSES { "const", "attribute", "varying", "uniform", "in", "out", "inout" }; 47 | 48 | QSet FUNCTIONS { 49 | "radians" 50 | , "degrees" 51 | , "sin" 52 | , "cos" 53 | , "tan" 54 | , "asin" 55 | , "acos" 56 | , "atan" 57 | , "pow" 58 | , "exp2" 59 | , "log2" 60 | , "sqrt" 61 | , "inversesqrt" 62 | , "abs" 63 | , "sign" 64 | , "floor" 65 | , "ceil" 66 | , "fract" 67 | , "mod" 68 | , "min" 69 | , "max" 70 | , "clamp" 71 | , "mix" 72 | , "step" 73 | , "smoothstep" 74 | , "length" 75 | , "distance" 76 | , "dot" 77 | , "cross" 78 | , "normalize" 79 | , "ftransform" 80 | , "faceforward" 81 | , "reflect" 82 | , "matrixcompmult" 83 | , "lessThan" 84 | , "lessThanEqual" 85 | , "greaterThan" 86 | , "greaterThanEqual" 87 | , "equal" 88 | , "notEqual" 89 | , "any" 90 | , "all" 91 | , "not" 92 | , "texture1D" 93 | , "texture1DProj" 94 | , "texture1DLod" 95 | , "texture1DProjLod" 96 | , "texture2D" 97 | , "texture2DProj" 98 | , "texture2DLod" 99 | , "texture2DProjLod" 100 | , "texture3D" 101 | , "texture3DProj" 102 | , "texture3DLod" 103 | , "texture3DProjLod" 104 | , "textureCube" 105 | , "textureCubeLod" 106 | , "shadow1D" 107 | , "shadow1DProj" 108 | , "shadow1DLod" 109 | , "shadow1DProjLod" 110 | , "shadow2D" 111 | , "shadow2DProj" 112 | , "shadow2DLod" 113 | , "shadow2DProjLod" 114 | , "dFdx" 115 | , "dFdy" 116 | , "fwidth" 117 | , "noise1" 118 | , "noise2" 119 | , "noise3" 120 | , "noise4" 121 | , "refract" 122 | , "exp" 123 | , "log" 124 | }; 125 | 126 | QSet STATES { 127 | "gl_Position" 128 | , "gl_PointSize" 129 | , "gl_ClipVertex" 130 | , "gl_FragCoord" 131 | , "gl_FrontFacing" 132 | , "gl_FragColor" 133 | , "gl_FragData" 134 | , "gl_FragDepth" 135 | , "gl_Color" 136 | , "gl_SecondaryColor" 137 | , "gl_Normal" 138 | , "gl_Vertex" 139 | , "gl_FogCoord" 140 | , "gl_FrontColor" 141 | , "gl_BackColor" 142 | , "gl_FrontSecondaryColor" 143 | , "gl_BackSecondaryColor" 144 | , "gl_TexCoord" 145 | , "gl_FogFragCoord" 146 | , "gl_MultiTexCoord0" 147 | , "gl_MultiTexCoord1" 148 | , "gl_MultiTexCoord2" 149 | , "gl_MultiTexCoord3" 150 | , "gl_MultiTexCoord4" 151 | , "gl_MultiTexCoord5" 152 | , "gl_MultiTexCoord6" 153 | , "gl_MultiTexCoord7" 154 | }; 155 | 156 | QSet UNIFORMS { 157 | "gl_ModelViewMatrix" 158 | , "gl_ProjectionMatrix" 159 | , "gl_ModelViewProjectionMatrix" 160 | , "gl_NormalMatrix" 161 | , "gl_TextureMatrix" 162 | , "gl_NormalScale" 163 | , "gl_DepthRange" 164 | , "gl_ClipPlane" 165 | , "gl_Point" 166 | , "gl_FrontMaterial" 167 | , "gl_BackMaterial" 168 | , "gl_LightSource" 169 | , "gl_LightModel" 170 | , "gl_FrontLightModelProduct" 171 | , "gl_BackLightModelProduct" 172 | , "gl_FrontLightProduct" 173 | , "gl_BackLightProduct" 174 | , "glTextureEnvColor" 175 | , "gl_TextureEnvColor" 176 | , "gl_Fog" 177 | , "gl_ModelViewMatrixInverse" 178 | , "gl_ProjectionMatrixInverse" 179 | , "gl_ModelViewProjectionMatrixInverse" 180 | , "gl_TextureMatrixInverse" 181 | , "gl_ModelViewMatrixTranspose" 182 | , "gl_ProjectionMatrixTranspose" 183 | , "gl_ModelViewProjectionMatrixTranspose" 184 | , "gl_TextureMatrixTranspose" 185 | , "gl_ModelViewMatrixInverseTranspose" 186 | , "gl_ProjectionMatrixInverseTranspose" 187 | , "gl_ModelViewProjectionMatrixInverseTranspose" 188 | , "gl_TextureMatrixInverseTranspose" 189 | , "gl_EyePlane" 190 | , "gl_ObjectPlane" 191 | 192 | // Qt uniforms 193 | , "qt_TexCoord0" 194 | , "qt_Opacity" 195 | , "qt_MultiTexCoord0" 196 | , "qt_Matrix" 197 | , "qt_Vertex" 198 | , "source" 199 | , "coord" 200 | 201 | // my uniforms 202 | , "source" 203 | , "u_resolution" 204 | , "u_mouse" 205 | , "u_time" 206 | 207 | , "TEXCOORD" 208 | , "TEXCOORD0" 209 | , "SV_POSITION" 210 | , "true" 211 | , "false" 212 | }; 213 | 214 | } 215 | 216 | Dictionary::Dictionary() 217 | { 218 | } 219 | 220 | QSet &Dictionary::conditionals() 221 | { 222 | return ::CONDITIONALS; 223 | } 224 | 225 | QSet &Dictionary::statements() 226 | { 227 | return ::STATEMENTS; 228 | } 229 | 230 | QSet &Dictionary::repeats() 231 | { 232 | return ::REPEATS; 233 | } 234 | 235 | QSet &Dictionary::types() 236 | { 237 | return ::TYPES; 238 | } 239 | 240 | QSet &Dictionary::starageClasses() 241 | { 242 | return ::STORAGE_CLASSES; 243 | } 244 | 245 | QSet &Dictionary::functions() 246 | { 247 | return ::FUNCTIONS; 248 | } 249 | 250 | QSet &Dictionary::states() 251 | { 252 | return ::STATES; 253 | } 254 | 255 | QSet &Dictionary::uniforms() 256 | { 257 | return ::UNIFORMS; 258 | } 259 | -------------------------------------------------------------------------------- /GlslHighlighter.cpp: -------------------------------------------------------------------------------- 1 | #include "GlslHighlighter.h" 2 | 3 | #include "Dictionary.h" 4 | 5 | GlslHighlighter::GlslHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent) 6 | { 7 | HighlightingRule rule; 8 | 9 | /////////////// 10 | /// FORMATS /// 11 | /////////////// 12 | 13 | statementFormat.setForeground(QColor(249, 38, 106)); 14 | 15 | commentFormat.setForeground(QColor(117, 113, 79)); 16 | 17 | preprocessorFormat.setForeground(QColor(117, 113, 79)); 18 | 19 | numberFormat.setForeground(QColor(174, 129, 227)); 20 | 21 | typesFormat.setForeground(QColor(88, 217, 239)); 22 | 23 | keywordFormat.setForeground(QColor(249, 38, 106)); 24 | 25 | uniformsFormat.setForeground(QColor(174, 129, 227)); 26 | 27 | swizzleFormat.setForeground(QColor(249, 38, 106)); 28 | 29 | ///////////// 30 | /// RULES /// 31 | ///////////// 32 | 33 | addPatternFromSet(Dictionary::conditionals(), statementFormat); 34 | 35 | addPatternFromSet(Dictionary::statements(), statementFormat); 36 | 37 | addPatternFromSet(Dictionary::repeats(), statementFormat); 38 | 39 | /* Numbers */ 40 | rule.pattern = QRegExp("\\b\\d+(u{,1}l{0,2}|ll{,1}u)\\b"); 41 | rule.format = numberFormat; 42 | highlightingRules.append(rule); 43 | rule.pattern = QRegExp("\\b0x\\x+(u{,1}l{0,2}|ll{,1}u)\\b"); 44 | rule.format = numberFormat; 45 | highlightingRules.append(rule); 46 | rule.pattern = QRegExp("\\b\\d+f\\b"); 47 | rule.format = numberFormat; 48 | highlightingRules.append(rule); 49 | rule.pattern = QRegExp("\\b\\d+\\.\\d*(e[-+]{,1}\\d+){,1}[fl]{,1}\\b"); 50 | rule.format = numberFormat; 51 | highlightingRules.append(rule); 52 | rule.pattern = QRegExp("\\b\\.\\d+(e[-+]{,1}\\d+){,1}[fl]{,1}\\b"); 53 | rule.format = numberFormat; 54 | highlightingRules.append(rule); 55 | rule.pattern = QRegExp("\\b\\d+e[-+]{,1}\\d+[fl]{,1}\\b"); 56 | rule.format = numberFormat; 57 | highlightingRules.append(rule); 58 | rule.pattern = QRegExp("\\b0\\o*[89]\\d*\\b"); 59 | rule.format = numberFormat; 60 | highlightingRules.append(rule); 61 | 62 | /* Swizzles */ 63 | rule.pattern = QRegExp("\\.[xyzw]{1,4}\\b"); 64 | rule.format = swizzleFormat; 65 | highlightingRules.append(rule); 66 | rule.pattern = QRegExp("\\.[rgba]{1,4}\\b"); 67 | rule.format = swizzleFormat; 68 | highlightingRules.append(rule); 69 | rule.pattern = QRegExp("\\.[stpq]{1,4}\\b"); 70 | rule.format = swizzleFormat; 71 | highlightingRules.append(rule); 72 | 73 | /* Types */ 74 | addPatternFromSet(Dictionary::types(), typesFormat); 75 | 76 | /* Storage class */ 77 | addPatternFromSet(Dictionary::starageClasses(), keywordFormat); 78 | 79 | /* Functions */ 80 | addPatternFromSet(Dictionary::functions(), statementFormat); 81 | 82 | /* States */ 83 | addPatternFromSet(Dictionary::states(), uniformsFormat); 84 | 85 | /* Uniforms */ 86 | addPatternFromSet(Dictionary::uniforms(), uniformsFormat); 87 | 88 | /* preprocessor */ 89 | rule.pattern = QRegExp("#.*"); 90 | rule.format = preprocessorFormat; 91 | highlightingRules.append(rule); 92 | 93 | /* single line comments */ 94 | rule.pattern = QRegExp("//.*"); 95 | rule.format = commentFormat; 96 | highlightingRules.append(rule); 97 | 98 | /* multi line comments */ 99 | commentStartExpression = QRegExp("/\\*"); 100 | commentEndExpression = QRegExp("\\*/"); 101 | } 102 | 103 | QQuickTextDocument *GlslHighlighter::quickTextDocument() const 104 | { 105 | return m_quickTextDocument; 106 | } 107 | 108 | void GlslHighlighter::setQuickTextDocument(QQuickTextDocument *quickTextDocument) 109 | { 110 | if (m_quickTextDocument != quickTextDocument) 111 | { 112 | m_quickTextDocument = quickTextDocument; 113 | QSyntaxHighlighter::setDocument(quickTextDocument->textDocument()); 114 | 115 | quickTextDocumentChanged(quickTextDocument); 116 | } 117 | 118 | } 119 | 120 | void GlslHighlighter::addPatternFromSet(QSet &set, QTextCharFormat &format) 121 | { 122 | HighlightingRule rule; 123 | 124 | QSet::iterator it; 125 | for (it = set.begin(); it != set.end(); ++it) 126 | { 127 | rule.pattern = QRegExp(QString("\\b") + (*it) + QString("\\b")); 128 | rule.format = format; 129 | highlightingRules.append(rule); 130 | } 131 | } 132 | 133 | void GlslHighlighter::highlightBlock(const QString &text) 134 | { 135 | foreach (HighlightingRule rule, highlightingRules) 136 | { 137 | QRegExp &expression = rule.pattern; 138 | int index = rule.pattern.indexIn(text); 139 | while (index >= 0) 140 | { 141 | int length = expression.matchedLength(); 142 | if (length <= 0) 143 | { 144 | fprintf(stderr, 145 | "length==0 for " "%s" " mathcing in " "%s" " at %d\n", 146 | expression.pattern().toLatin1().data(), 147 | text.toLatin1().data(), index); 148 | } 149 | setFormat(index, length, rule.format); 150 | index = expression.indexIn(text, index + length); 151 | } 152 | } 153 | setCurrentBlockState(0); 154 | 155 | int startIndex = 0; 156 | if (previousBlockState() != 1) 157 | { 158 | startIndex = commentStartExpression.indexIn(text); 159 | } 160 | 161 | while (startIndex >= 0) 162 | { 163 | int endIndex = commentEndExpression.indexIn(text, startIndex); 164 | int commentLength; 165 | if (endIndex == -1) 166 | { 167 | setCurrentBlockState(1); 168 | commentLength = text.length() - startIndex; 169 | } 170 | else 171 | { 172 | commentLength = endIndex - startIndex + commentEndExpression.matchedLength(); 173 | } 174 | setFormat(startIndex, commentLength, commentFormat); 175 | startIndex = commentStartExpression.indexIn(text, startIndex + commentLength); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /resources/qml/uniforms/Uniform.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.12 3 | 4 | import com.dln.PropertyHandler 1.0 5 | 6 | Rectangle { 7 | id: root 8 | 9 | property alias name: nameText.text 10 | property int type: 0 11 | property var value: undefined 12 | property alias actionIcon: actionButton.icon.source 13 | property bool readOnly: false 14 | 15 | function reset() { 16 | root.name = "" 17 | contentLoader.reset() 18 | } 19 | 20 | signal actionClicked(string name, int type, var value) 21 | signal valueModified(var value) 22 | 23 | QtObject { 24 | id: internal 25 | 26 | function handleType(floatType, sampler2dType, vector2dType, vector3dType, vector4dType, matrix4x4Type) { 27 | switch(root.type) { 28 | case PropertyHandler.Float: return floatType 29 | case PropertyHandler.Sampler2d: return sampler2dType 30 | case PropertyHandler.Vector2d: return vector2dType 31 | case PropertyHandler.Vector3d: return vector3dType 32 | case PropertyHandler.Vector4d: return vector4dType 33 | case PropertyHandler.Matrix4x4: return matrix4x4Type 34 | default: return null 35 | } 36 | } 37 | } 38 | 39 | height: headerItem.height + nameText.height + contentLoader.height + 5 + 5 + 5 40 | radius: 8 41 | color: "#33352f" 42 | 43 | Item { // different types based on readOnly 44 | id: headerItem 45 | 46 | width: parent.width 47 | height: 30 48 | 49 | Text { 50 | visible: root.readOnly // TODO: loader 51 | anchors.fill: parent 52 | text: internal.handleType("Float", "Sampler2d", "Vector2d", "Vector3d", "Vector4d", "Matrix4x4") 53 | verticalAlignment: Text.AlignVCenter 54 | leftPadding: 15 55 | color: "white" 56 | } 57 | 58 | ComboBox { 59 | id: combo 60 | 61 | visible: !root.readOnly // TODO: loader 62 | anchors.fill: parent 63 | textRole: "text" 64 | valueRole: "value" 65 | currentIndex: root.type 66 | 67 | indicator: Image { 68 | anchors { 69 | right: parent.right 70 | verticalCenter: parent.verticalCenter 71 | rightMargin: 5 72 | verticalCenterOffset: 2 73 | } 74 | opacity: combo.down ? 0.5 : 1.0 75 | source: "qrc:/resources/assets/images/down_arrow.png" 76 | } 77 | 78 | contentItem: Text { 79 | text: "Create uniform: " + combo.displayText 80 | verticalAlignment: Text.AlignVCenter 81 | leftPadding: 15 82 | } 83 | 84 | background: Item { 85 | clip: true 86 | 87 | Rectangle { 88 | anchors { 89 | fill: parent 90 | bottomMargin: -5 91 | } 92 | color: "#a8a8a1" 93 | radius: 5 94 | } 95 | } 96 | 97 | model: [ 98 | { value: PropertyHandler.Float, text: "Float" }, 99 | { value: PropertyHandler.Sampler2d, text: "Sampler2d" }, 100 | { value: PropertyHandler.Vector2d, text: "Vector2d" }, 101 | { value: PropertyHandler.Vector3d, text: "Vector3d"}, 102 | { value: PropertyHandler.Vector4d, text: "Vector4d" }, 103 | { value: PropertyHandler.Matrix4x4, text: "Matrix4x4" } 104 | ] 105 | 106 | onActivated: { 107 | root.type = currentValue 108 | nameText.forceActiveFocus() 109 | } 110 | } 111 | } 112 | 113 | TextField { 114 | id: nameText 115 | 116 | property int nameErrorCode: _dynamicPropertyHandler.nameErrorCode(root.name) 117 | 118 | anchors { 119 | left: parent.left 120 | right: actionButton.left 121 | top: headerItem.bottom 122 | margins: 5 123 | } 124 | height: 30 125 | color: "black" 126 | selectedTextColor: "white" 127 | selectionColor: "dimgrey" 128 | selectByMouse: true 129 | placeholderText: "Set uniform name" 130 | readOnly: root.readOnly 131 | 132 | background: Rectangle { 133 | radius: 5 134 | color: nameText.activeFocus ? "#a8a8a1" : "#8b8b85" 135 | } 136 | 137 | ToolTip { 138 | x: -width - 15 139 | y: (parent.height - height) * 0.5 140 | visible: !root.readOnly && nameText.nameErrorCode !== PropertyHandler.Valid && nameText.nameErrorCode !== PropertyHandler.Empty 141 | text: _dynamicPropertyHandler.humanReadableNameErrorCode(nameText.nameErrorCode) 142 | } 143 | 144 | onAccepted: { 145 | if (!root.readOnly && actionButton.enabled) actionButton.clicked() 146 | } 147 | } 148 | 149 | UniformButton { 150 | id: actionButton 151 | 152 | anchors { 153 | right: parent.right 154 | top: headerItem.bottom 155 | margins: 5 156 | } 157 | enabled: root.readOnly || nameText.nameErrorCode === PropertyHandler.Valid 158 | 159 | onClicked: { 160 | root.actionClicked(root.name, root.type, contentLoader.item.uniformValue) 161 | } 162 | } 163 | 164 | Loader { 165 | id: contentLoader 166 | 167 | function reset() { 168 | active = false 169 | active = true 170 | } 171 | 172 | anchors { 173 | left: parent.left 174 | right: parent.right 175 | top: nameText.bottom 176 | margins: 5 177 | } 178 | 179 | sourceComponent: internal.handleType(floatComponent, sampler2dComponent, vector2dComponent, vector3dComponent, vector4dComponent, matrix4x4Component) 180 | 181 | onItemChanged: { 182 | if (item && root.value !== undefined) { 183 | item.uniformValue = root.value 184 | } 185 | } 186 | 187 | Connections { 188 | target: contentLoader.item 189 | 190 | onUniformValueChanged: { 191 | root.valueModified(contentLoader.item.uniformValue) 192 | } 193 | } 194 | } 195 | 196 | // Float uniform 197 | Component { 198 | id: floatComponent 199 | 200 | FloatUniform { 201 | } 202 | } 203 | 204 | // Sampler2d uniform 205 | Component { 206 | id: sampler2dComponent 207 | 208 | Sampler2dUniform { 209 | } 210 | } 211 | 212 | // Vector2d uniform 213 | Component { 214 | id: vector2dComponent 215 | 216 | Vector2dUniform { 217 | } 218 | } 219 | 220 | // Vector3d uniform 221 | Component { 222 | id: vector3dComponent 223 | 224 | Vector3dUniform { 225 | } 226 | } 227 | 228 | // Vector4d uniform 229 | Component { 230 | id: vector4dComponent 231 | 232 | Vector4dUniform { 233 | } 234 | } 235 | 236 | // Matrix4x4 uniform 237 | Component { 238 | id: matrix4x4Component 239 | 240 | Matrix4x4Uniform { 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /resources/qml/Shaders.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | 3 | import QtQuick 2.12 4 | 5 | QtObject { 6 | 7 | ///////////////////////////////////////////////////////////////////////////////////////// 8 | // 9 | // FRAGMENT SHADERS 10 | // 11 | ///////////////////////////////////////////////////////////////////////////////////////// 12 | 13 | readonly property string emptyShader: "\ 14 | #ifdef GL_ES 15 | precision lowp float; 16 | #endif 17 | 18 | uniform sampler2D source; 19 | uniform float qt_Opacity; 20 | varying highp vec2 qt_TexCoord0; 21 | 22 | uniform vec2 u_resolution; 23 | uniform vec2 u_mouse; 24 | uniform float u_time; 25 | 26 | void main() { 27 | vec4 p = texture2D(source, qt_TexCoord0); 28 | 29 | // some code here 30 | 31 | gl_FragColor = p * qt_Opacity; 32 | }" 33 | 34 | readonly property string emptyUvShader: "\ 35 | #ifdef GL_ES 36 | precision lowp float; 37 | #endif 38 | 39 | uniform sampler2D source; 40 | uniform float qt_Opacity; 41 | varying highp vec2 qt_TexCoord0; 42 | 43 | uniform vec2 u_resolution; 44 | uniform vec2 u_mouse; 45 | uniform float u_time; 46 | 47 | void main() { 48 | vec2 st = qt_TexCoord0; 49 | 50 | vec2 uv = st; // modify UV here 51 | 52 | gl_FragColor = texture2D(source, uv); 53 | }" 54 | 55 | readonly property string greyscaleShader: "\ 56 | #ifdef GL_ES 57 | precision lowp float; 58 | #endif 59 | 60 | uniform sampler2D source; 61 | uniform float qt_Opacity; 62 | varying highp vec2 qt_TexCoord0; 63 | 64 | uniform vec2 u_resolution; 65 | uniform vec2 u_mouse; 66 | uniform float u_time; 67 | 68 | void main() { 69 | vec4 p = texture2D(source, qt_TexCoord0); 70 | float g = dot(p.xyz, vec3(0.344, 0.5, 0.156)); 71 | gl_FragColor = vec4(g, g, g, p.a) * qt_Opacity; 72 | }" 73 | 74 | readonly property string simpleShader: "\ 75 | #ifdef GL_ES 76 | precision lowp float; 77 | #endif 78 | 79 | uniform sampler2D source; 80 | uniform float qt_Opacity; 81 | varying highp vec2 qt_TexCoord0; 82 | 83 | uniform vec2 u_resolution; 84 | uniform vec2 u_mouse; 85 | uniform float u_time; 86 | 87 | void main() { 88 | vec2 st = gl_FragCoord.xy / u_resolution; 89 | //vec2 st = qt_TexCoord0; 90 | 91 | gl_FragColor = vec4(st.x, st.y, 0.0, 1.0); 92 | }" 93 | 94 | readonly property string linearDemoShader: "\ 95 | #ifdef GL_ES 96 | precision lowp float; 97 | #endif 98 | 99 | uniform sampler2D source; 100 | uniform float qt_Opacity; 101 | varying highp vec2 qt_TexCoord0; 102 | 103 | uniform vec2 u_resolution; 104 | uniform vec2 u_mouse; 105 | uniform float u_time; 106 | 107 | // Plot a line on Y using a value between 0.0 - 1.0 108 | float plot(vec2 st) { 109 | return smoothstep(0.02, 0.0, abs(st.y - st.x)); 110 | } 111 | 112 | void main() { 113 | vec2 st = qt_TexCoord0; 114 | 115 | float y = st.x; 116 | 117 | vec3 color = vec3(y); 118 | 119 | // Plot a line 120 | float pct = plot(st); 121 | color = (1.0 - pct) * color + pct * vec3(0.0, 1.0, 0.0); 122 | 123 | gl_FragColor = vec4(color, 1.0); 124 | }" 125 | 126 | readonly property string expoDemoShader: "\ 127 | #ifdef GL_ES 128 | precision lowp float; 129 | #endif 130 | 131 | uniform sampler2D source; 132 | uniform float qt_Opacity; 133 | varying highp vec2 qt_TexCoord0; 134 | 135 | uniform vec2 u_resolution; 136 | uniform vec2 u_mouse; 137 | uniform float u_time; 138 | 139 | #define PI 3.14159265359 140 | 141 | float plot(vec2 st, float pct) { 142 | return smoothstep(pct - 0.02, pct, st.y) - smoothstep(pct, pct + 0.02, st.y); 143 | } 144 | 145 | void main() { 146 | vec2 st = qt_TexCoord0; 147 | 148 | float y = pow(st.x, 5.0); 149 | 150 | vec3 color = vec3(y); 151 | 152 | float pct = plot(st, y); 153 | color = (1.0 - pct) * color + pct * vec3(0.0, 1.0, 0.0); 154 | 155 | gl_FragColor = vec4(color, 1.0); 156 | }" 157 | 158 | readonly property string russiaFlagShader: "\ 159 | #ifdef GL_ES 160 | precision lowp float; 161 | #endif 162 | 163 | uniform sampler2D source; 164 | uniform float qt_Opacity; 165 | varying highp vec2 qt_TexCoord0; 166 | 167 | uniform vec2 u_resolution; 168 | uniform vec2 u_mouse; 169 | uniform float u_time; 170 | 171 | vec3 colorA = vec3(0.97, 0.97, 0.97); 172 | vec3 colorB = vec3(0.1, 0.1, 1.0); 173 | vec3 colorC = vec3(1.0, 0.03, 0.03); 174 | 175 | void main() { 176 | float y = qt_TexCoord0.y; 177 | 178 | //vec3 color = (1.0 - step(0.33, y)) * colorA + step(0.33, y) * (1.0 - step(0.66, y)) * colorB + step(0.66, y) * colorC; 179 | 180 | vec3 color = mix(mix(colorA, colorB, step(0.33, y)), colorC, step(0.66, y)); 181 | gl_FragColor = vec4(color, 1.0); 182 | }" 183 | 184 | readonly property string circleShader: "\ 185 | #ifdef GL_ES 186 | precision mediump float; 187 | #endif 188 | 189 | uniform sampler2D source; 190 | uniform float qt_Opacity; 191 | varying highp vec2 qt_TexCoord0; 192 | 193 | uniform vec2 u_resolution; 194 | uniform vec2 u_mouse; 195 | uniform float u_time; 196 | 197 | void main(){ 198 | vec2 st = qt_TexCoord0; 199 | st.y *= u_resolution.y / u_resolution.x; // adjust to keep the aspect ratio 200 | 201 | float pct = 0.0; 202 | 203 | // a. The DISTANCE from the pixel to the center 204 | pct = distance(st, vec2(0.5)); 205 | 206 | // b. The LENGTH of the vector 207 | // from the pixel to the center 208 | // vec2 toCenter = vec2(0.5) - st; 209 | // pct = length(toCenter); 210 | 211 | // c. The SQUARE ROOT of the vector 212 | // from the pixel to the center 213 | // vec2 tC = vec2(0.5) - st; 214 | // pct = sqrt(tC.x * tC.x + tC.y * tC.y); 215 | 216 | vec3 color = mix(vec3(1.0), vec3(0.0), step(0.35, pct)); 217 | 218 | gl_FragColor = vec4(color, 1.0); 219 | }" 220 | 221 | readonly property string magnifierGlassShader: "\ 222 | #ifdef GL_ES 223 | precision lowp float; 224 | #endif 225 | 226 | uniform sampler2D source; 227 | uniform float qt_Opacity; 228 | varying highp vec2 qt_TexCoord0; 229 | 230 | uniform vec2 u_resolution; 231 | uniform vec2 u_mouse; 232 | uniform float u_time; 233 | 234 | float plot(float x) { 235 | return 40.0 * pow(x, 4.0) + 0.8; 236 | } 237 | 238 | void main() { 239 | vec2 st = qt_TexCoord0 - u_mouse; 240 | 241 | float len = length(st); 242 | st = st * (len < 0.3 ? plot(len) : 1.0) + u_mouse; 243 | 244 | gl_FragColor = texture2D(source, st); 245 | }" 246 | 247 | readonly property string magicLensShader: "\ 248 | #ifdef GL_ES 249 | precision lowp float; 250 | #endif 251 | 252 | uniform sampler2D source; 253 | uniform float qt_Opacity; 254 | varying highp vec2 qt_TexCoord0; 255 | 256 | uniform vec2 u_resolution; 257 | uniform vec2 u_mouse; 258 | uniform float u_time; 259 | 260 | void main() { 261 | vec2 st = qt_TexCoord0 - u_mouse; 262 | 263 | vec2 uv = st; 264 | float len = length(st); 265 | uv = normalize(st) * clamp(0.0, 0.3, len); 266 | 267 | gl_FragColor = texture2D(source, uv + u_mouse); 268 | }" 269 | 270 | readonly property string distanceFieldShader: "\ 271 | #ifdef GL_ES 272 | precision lowp float; 273 | #endif 274 | 275 | uniform sampler2D source; 276 | uniform float qt_Opacity; 277 | varying highp vec2 qt_TexCoord0; 278 | 279 | uniform vec2 u_resolution; 280 | uniform vec2 u_mouse; 281 | uniform float u_time; 282 | 283 | void main(){ 284 | vec2 st = qt_TexCoord0; 285 | st.x *= u_resolution.x / u_resolution.y; 286 | vec3 color = vec3(0.0); 287 | float d = 0.0; 288 | 289 | // Remap the space to -1. to 1. 290 | st = st * 2.0 - 1.0; 291 | 292 | // Make the distance field 293 | d = length(abs(st) - 0.3); 294 | // d = length(min(abs(st) - 0.3, 0.0)); 295 | // d = length(max(abs(st) - 0.3, 0.0)); 296 | 297 | // Visualize the distance field 298 | gl_FragColor = vec4(vec3(fract(d * 10.0)), 1.0); 299 | 300 | // Drawing with the distance field 301 | // gl_FragColor = vec4(vec3(step(0.3, d) ),1.0); 302 | // gl_FragColor = vec4(vec3(step(0.3, d) * step(d, 0.4)), 1.0); 303 | // gl_FragColor = vec4(vec3(smoothstep(0.3, 0.4, d) * smoothstep(0.6, 0.5, d)), 1.0); 304 | }" 305 | 306 | readonly property string polarCoordinatesShader1: "\ 307 | #ifdef GL_ES 308 | precision lowp float; 309 | #endif 310 | 311 | uniform sampler2D source; 312 | uniform float qt_Opacity; 313 | varying highp vec2 qt_TexCoord0; 314 | 315 | uniform vec2 u_resolution; 316 | uniform vec2 u_mouse; 317 | uniform float u_time; 318 | 319 | #define PI 3.14159265358979323844 320 | 321 | void main() { 322 | vec2 st = qt_TexCoord0; 323 | 324 | vec2 pos = st - vec2(0.5); 325 | float r = length(pos) * 2.0; 326 | float a = atan(pos.y, pos.x); 327 | 328 | vec2 uv = vec2(r, a * 0.5 / PI + 0.5); 329 | gl_FragColor = texture2D(source, uv); 330 | }" 331 | 332 | readonly property string polarCoordinatesShader2: "\ 333 | #ifdef GL_ES 334 | precision lowp float; 335 | #endif 336 | 337 | uniform sampler2D source; 338 | uniform float qt_Opacity; 339 | varying highp vec2 qt_TexCoord0; 340 | 341 | uniform vec2 u_resolution; 342 | uniform vec2 u_mouse; 343 | uniform float u_time; 344 | 345 | #define PI 3.14159265358979323844 346 | 347 | void main(){ 348 | vec2 st = qt_TexCoord0; 349 | vec3 color = vec3(0.0); 350 | 351 | vec2 pos = vec2(0.5) - st; 352 | 353 | float r = length(pos) * 2.0; 354 | float a = atan(pos.y, pos.x); 355 | 356 | float f = cos(a * 3.0); 357 | // f = abs(cos(a * 3.0)); 358 | // f = abs(cos(a * 2.5)) * 0.5 + 0.3; 359 | // f = abs(cos(a * 12.0)*sin(a * 3.0)) * 0.8 + 0.1; 360 | // f = smoothstep(-0.5, 1.0, cos(a * 10.0)) * 0.2 + 0.5; 361 | 362 | color = vec3(1.0 - smoothstep(f, f + 0.02, r)); 363 | 364 | gl_FragColor = vec4(color, 1.0); 365 | }" 366 | 367 | readonly property string rippleEffectShader1: "\ 368 | #ifdef GL_ES 369 | precision lowp float; 370 | #endif 371 | 372 | #define PI 3.14159265359 373 | 374 | uniform sampler2D source; 375 | uniform float qt_Opacity; 376 | varying highp vec2 qt_TexCoord0; 377 | 378 | uniform vec2 u_resolution; 379 | uniform vec2 u_mouse; 380 | uniform float u_time; 381 | 382 | void main() { 383 | vec2 st = qt_TexCoord0; 384 | vec2 uv = vec2(st.x + 0.015 * sin(10.0 * PI * st.y + 5.0 * u_time), st.y + 0.015 * sin(10.0 * PI * st.x + 5.0 * u_time)); // modify UV here 385 | gl_FragColor = texture2D(source, uv); 386 | }" 387 | 388 | readonly property string rippleEffectShader2: "\ 389 | #ifdef GL_ES 390 | precision lowp float; 391 | #endif 392 | 393 | uniform sampler2D source; 394 | uniform float qt_Opacity; 395 | varying highp vec2 qt_TexCoord0; 396 | 397 | uniform vec2 u_resolution; 398 | uniform vec2 u_mouse; 399 | uniform float u_time; 400 | 401 | void main(){ 402 | float intensity = 0.06; 403 | vec2 p = -1.0 + 2.0 * qt_TexCoord0 - u_mouse + 0.5; 404 | float cLength = length(p); 405 | 406 | vec2 uv = qt_TexCoord0 + (p / cLength) * cos(cLength * 15.0 - u_time * 4.0) * intensity; 407 | 408 | gl_FragColor = texture2D(source, uv); 409 | }" 410 | 411 | readonly property string heatHazeAirShader: "\ 412 | #ifdef GL_ES 413 | precision lowp float; 414 | #endif 415 | 416 | uniform sampler2D source; 417 | uniform float qt_Opacity; 418 | varying highp vec2 qt_TexCoord0; 419 | 420 | uniform vec2 u_resolution; 421 | uniform vec2 u_mouse; 422 | uniform float u_time; 423 | 424 | float noise(float x) 425 | { 426 | return sin(x * 100.0) * 0.1 + sin((x * 200.0) + 3.0) * 0.05 + 427 | fract(sin((x * 19.0) + 1.0) * 33.33) * 0.13; 428 | } 429 | 430 | void main() 431 | { 432 | vec2 p_m = qt_TexCoord0; 433 | vec2 p_d = p_m; 434 | 435 | p_d.xy -= u_time * 0.1; 436 | 437 | vec2 dst_map_val = vec2(noise(p_d.y), noise(p_d.x)); 438 | 439 | vec2 dst_offset = dst_map_val.xy; 440 | dst_offset -= vec2(0.5, 0.5); 441 | dst_offset *= 2.0; 442 | dst_offset *= 0.01; 443 | 444 | //reduce effect towards Y top 445 | dst_offset *= (1.0 - p_m.t); 446 | 447 | vec2 dist_tex_coord = p_m.st + dst_offset; 448 | gl_FragColor = texture2D(source, dist_tex_coord); 449 | }" 450 | 451 | readonly property string fractalShader: "\ 452 | #ifdef GL_ES 453 | precision lowp float; 454 | #endif 455 | 456 | uniform sampler2D source; 457 | uniform float qt_Opacity; 458 | varying highp vec2 qt_TexCoord0; 459 | 460 | uniform vec2 u_resolution; 461 | uniform vec2 u_mouse; 462 | uniform float u_time; 463 | 464 | void main() { 465 | vec2 c = (2.0 * qt_TexCoord0 - vec2(1.5, 1.0)); 466 | vec2 z = vec2(0.0); 467 | 468 | float i = 0.0; 469 | for(; i <= 64.0 && dot(z, z) < 4.0; ++i) { 470 | z = mat2(z, -z.y, z.x) * z + c; 471 | } 472 | 473 | gl_FragColor = vec4(vec3(i / 64.0), 1.0); 474 | }" 475 | 476 | readonly property string complexFogShader: "\ 477 | #ifdef GL_ES 478 | precision lowp float; 479 | #endif 480 | 481 | uniform sampler2D source; 482 | uniform float qt_Opacity; 483 | varying highp vec2 qt_TexCoord0; 484 | 485 | uniform vec2 u_resolution; 486 | uniform vec2 u_mouse; 487 | uniform float u_time; 488 | 489 | float random(in vec2 _st) { 490 | return fract(sin(dot(_st.xy, vec2(12.9898, 78.233))) * 43758.5453123); 491 | } 492 | 493 | // Based on Morgan McGuire @morgan3d 494 | // https://www.shadertoy.com/view/4dS3Wd 495 | float noise (in vec2 _st) { 496 | vec2 i = floor(_st); 497 | vec2 f = fract(_st); 498 | 499 | // Four corners in 2D of a tile 500 | float a = random(i); 501 | float b = random(i + vec2(1.0, 0.0)); 502 | float c = random(i + vec2(0.0, 1.0)); 503 | float d = random(i + vec2(1.0, 1.0)); 504 | 505 | vec2 u = f * f * (3.0 - 2.0 * f); 506 | 507 | return mix(a, b, u.x) + 508 | (c - a)* u.y * (1.0 - u.x) + 509 | (d - b) * u.x * u.y; 510 | } 511 | 512 | #define NUM_OCTAVES 5 513 | 514 | float fbm(in vec2 _st) { 515 | float v = 0.0; 516 | float a = 0.5; 517 | vec2 shift = vec2(100.0); 518 | // Rotate to reduce axial bias 519 | mat2 rot = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.50)); 520 | for (int i = 0; i < NUM_OCTAVES; ++i) { 521 | v += a * noise(_st); 522 | _st = rot * _st * 2.0 + shift; 523 | a *= 0.5; 524 | } 525 | return v; 526 | } 527 | 528 | void main() { 529 | vec2 st = qt_TexCoord0 * 3.0; 530 | // st += st * abs(sin(u_time * 0.1) * 3.0); 531 | vec3 color = vec3(0.0); 532 | 533 | vec2 q = vec2(0.0); 534 | q.x = fbm(st + 0.00 * u_time); 535 | q.y = fbm(st + vec2(1.0)); 536 | 537 | vec2 r = vec2(0.0); 538 | r.x = fbm(st + 1.0 * q + vec2(1.7, 9.2) + 0.15 * u_time); 539 | r.y = fbm(st + 1.0 * q + vec2(8.3, 2.8) + 0.126 * u_time); 540 | 541 | float f = fbm(st + r); 542 | 543 | color = mix(vec3(0.101961, 0.619608, 0.666667), 544 | vec3(0.666667, 0.666667, 0.498039), 545 | clamp((f * f) * 4.0, 0.0, 1.0)); 546 | 547 | color = mix(color, 548 | vec3(0.0, 0.0, 0.164706), 549 | clamp(length(q), 0.0, 1.0)); 550 | 551 | color = mix(color, 552 | vec3(0.666667, 1.0, 1.0), 553 | clamp(length(r.x), 0.0, 1.0)); 554 | 555 | gl_FragColor = vec4((f * f * f + 0.6 * f * f + 0.5 * f) * color, 1.0); 556 | }" 557 | 558 | 559 | ///////////////////////////////////////////////////////////////////////////////////////// 560 | // 561 | // VERTEX SHADERS 562 | // 563 | ///////////////////////////////////////////////////////////////////////////////////////// 564 | 565 | readonly property string defaultVertexShader: "\ 566 | uniform highp mat4 qt_Matrix; 567 | attribute highp vec4 qt_Vertex; 568 | attribute highp vec2 qt_MultiTexCoord0; 569 | varying highp vec2 qt_TexCoord0; 570 | 571 | uniform vec2 u_resolution; 572 | uniform vec2 u_mouse; 573 | uniform float u_time; 574 | 575 | void main() { 576 | qt_TexCoord0 = qt_MultiTexCoord0; 577 | gl_Position = qt_Matrix * qt_Vertex; 578 | }" 579 | } 580 | --------------------------------------------------------------------------------