320 lines
10 KiB
QML
320 lines
10 KiB
QML
// Copyright (C) 2023 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
import QtQuick
|
|
import QtQuick.Window
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
import QtQuick.Dialogs
|
|
import QtCore
|
|
|
|
import QtQuick3D.MaterialEditor
|
|
|
|
ApplicationWindow {
|
|
id: window
|
|
height: 720
|
|
width: 1024
|
|
visible: true
|
|
title: qsTr("Custom Material Editor")
|
|
|
|
// Context property (see main.cpp)
|
|
property url projectFolder: _qtProjectDir // qmllint disable unqualified
|
|
|
|
Settings {
|
|
id: settings
|
|
property alias windowX: window.x
|
|
property alias windowY: window.y
|
|
property alias windowWidth: window.width
|
|
property alias windowHeight: window.height
|
|
property alias windowVisibility: window.visibility
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
mainSplitView.restoreState(settings.value("ui/mainSplitView"))
|
|
editorView.restoreState(settings.value("ui/editorView"))
|
|
}
|
|
Component.onDestruction: {
|
|
settings.setValue("ui/mainSplitView", mainSplitView.saveState())
|
|
settings.setValue("ui/editorView", editorView.saveState())
|
|
}
|
|
|
|
QtObject {
|
|
id: resourceStore
|
|
objectName: "QtQuick3DResourceStorePrivate"
|
|
}
|
|
|
|
FileDialog {
|
|
id: openMaterialDialog
|
|
title: "Open a Material Project File"
|
|
nameFilters: [ "Material Editor Project (*.qmp)"]
|
|
currentFolder: window.projectFolder
|
|
onAccepted: {
|
|
if (openMaterialDialog.selectedFile !== null)
|
|
materialAdapter.loadMaterial(openMaterialDialog.selectedFile);
|
|
}
|
|
}
|
|
|
|
FileDialog {
|
|
id: saveAsDialog
|
|
fileMode: FileDialog.SaveFile
|
|
currentFolder: window.projectFolder
|
|
nameFilters: [ "Material Editor Project (*.qmp)"]
|
|
onAccepted: materialAdapter.saveMaterial(selectedFile)
|
|
|
|
}
|
|
|
|
|
|
FileDialog {
|
|
id: fragmentShaderImportDialog
|
|
title: "Fragment Shader to import"
|
|
nameFilters: [ "Fragment Shader (*.frag *.fs *.glsl)" ]
|
|
currentFolder: window.projectFolder
|
|
onAccepted: {
|
|
if (fragmentShaderImportDialog.selectedFile !== null) {
|
|
materialAdapter.importFragmentShader(fragmentShaderImportDialog.selectedFile)
|
|
}
|
|
}
|
|
}
|
|
|
|
FileDialog {
|
|
id: vertexShaderImportDialog
|
|
title: "Vertex Shader to import"
|
|
nameFilters: [ "Vertex Shader (*.vert *.vs *.glsl)" ]
|
|
currentFolder: window.projectFolder
|
|
onAccepted: {
|
|
if (vertexShaderImportDialog.selectedFile !== null) {
|
|
materialAdapter.importVertexShader(vertexShaderImportDialog.selectedFile)
|
|
}
|
|
}
|
|
}
|
|
|
|
FileDialog {
|
|
id: saveCompFileDialog
|
|
title: "Choose file"
|
|
nameFilters: [ "QML Componen (*.qml)" ]
|
|
fileMode: FileDialog.SaveFile
|
|
currentFolder: window.projectFolder
|
|
onAccepted: {
|
|
if (selectedFile !== null)
|
|
componentFilePath.text = selectedFile
|
|
}
|
|
}
|
|
|
|
RegularExpressionValidator {
|
|
id: nameValidator
|
|
regularExpression: /[a-zA-Z0-9_-]*/
|
|
}
|
|
|
|
Dialog {
|
|
id: exportMaterialDialog
|
|
title: "Export material"
|
|
anchors.centerIn: parent
|
|
|
|
ColumnLayout {
|
|
id: exportFiles
|
|
anchors.fill: parent
|
|
spacing: 1
|
|
RowLayout {
|
|
Text {
|
|
text: qsTr("Component")
|
|
color: palette.text
|
|
}
|
|
TextField {
|
|
id: componentFilePath
|
|
readOnly: true
|
|
}
|
|
Button {
|
|
text: qsTr("Choose...")
|
|
onClicked: {
|
|
saveCompFileDialog.open()
|
|
exportMaterialDialog.aboutToHide()
|
|
}
|
|
}
|
|
}
|
|
RowLayout {
|
|
Text {
|
|
text: qsTr("Vertex:")
|
|
color: palette.text
|
|
}
|
|
TextField {
|
|
id: vertexFilename
|
|
enabled: (editorView.vertexEditor.text !== "")
|
|
validator: nameValidator
|
|
}
|
|
}
|
|
RowLayout {
|
|
Text {
|
|
text: qsTr("Fragment:")
|
|
color: palette.text
|
|
}
|
|
TextField {
|
|
id: fragmentFilename
|
|
enabled: (editorView.fragmentEditor.text !== "")
|
|
validator: nameValidator
|
|
}
|
|
}
|
|
|
|
DialogButtonBox {
|
|
Button {
|
|
text: qsTr("Export")
|
|
enabled: (componentFilePath.text !== "" && (!vertexFilename.enabled || (vertexFilename.enabled && vertexFilename.text !== "")) && (!fragmentFilename.enabled || (fragmentFilename.enabled && fragmentFilename.text !== "")))
|
|
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
|
|
onClicked: exportMaterialDialog.accept()
|
|
}
|
|
Button {
|
|
text: qsTr("Cancel")
|
|
DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole
|
|
onClicked: exportMaterialDialog.reject()
|
|
}
|
|
}
|
|
}
|
|
|
|
onAccepted: {
|
|
materialAdapter.exportQmlComponent(componentFilePath.text, vertexFilename.text, fragmentFilename.text)
|
|
}
|
|
}
|
|
|
|
SaveChangesDialog {
|
|
id: saveChangesDialog
|
|
materialAdapter: materialAdapter
|
|
saveAsDialog: saveAsDialog
|
|
anchors.centerIn: parent
|
|
}
|
|
|
|
AboutDialog {
|
|
id: aboutDialog
|
|
parent: Overlay.overlay
|
|
anchors.centerIn: parent
|
|
}
|
|
|
|
function saveAction() {
|
|
// 1. No file name(s) given (call saveAs)
|
|
let materialSaveFileUrl = new URL(materialAdapter.materialSaveFile)
|
|
if (materialSaveFileUrl.toString().length > 0)
|
|
materialAdapter.save()
|
|
else
|
|
saveAsAction()
|
|
}
|
|
function openAction() {
|
|
openMaterialDialog.open()
|
|
}
|
|
function newAction() {
|
|
saveChangesDialog.doIfChangesSavedOrDiscarded(() => { materialAdapter.reset() });
|
|
materialAdapter.reset()
|
|
}
|
|
function saveAsAction() {
|
|
saveAsDialog.open()
|
|
}
|
|
function quitAction() {
|
|
Qt.quit()
|
|
}
|
|
function aboutAction() {
|
|
aboutDialog.open()
|
|
}
|
|
|
|
function importFragmentShader() {
|
|
fragmentShaderImportDialog.open()
|
|
}
|
|
|
|
function importVertexShader() {
|
|
vertexShaderImportDialog.open()
|
|
}
|
|
|
|
function exportMaterial() {
|
|
exportMaterialDialog.open()
|
|
}
|
|
|
|
menuBar: MenuBar {
|
|
Menu {
|
|
title: qsTr("&File")
|
|
Action { text: qsTr("&New..."); onTriggered: window.newAction(); }
|
|
Action { text: qsTr("&Open..."); onTriggered: window.openAction(); }
|
|
Action { text: qsTr("&Save"); onTriggered: window.saveAction(); }
|
|
Action { text: qsTr("Save &As..."); onTriggered: window.saveAsAction(); }
|
|
MenuSeparator { }
|
|
Menu {
|
|
title: qsTr("Import")
|
|
Action { text: qsTr("Fragment Shader"); onTriggered: window.importFragmentShader(); }
|
|
Action { text: qsTr("Vertex Shader"); onTriggered: window.importVertexShader(); }
|
|
}
|
|
Action { text: qsTr("Export"); onTriggered: window.exportMaterial(); }
|
|
|
|
MenuSeparator { }
|
|
Action { text: qsTr("&Quit"); onTriggered: window.quitAction(); }
|
|
}
|
|
Menu {
|
|
title: qsTr("&Help")
|
|
Action { text: qsTr("&About"); onTriggered: window.aboutAction(); }
|
|
}
|
|
}
|
|
|
|
SplitView {
|
|
id: mainSplitView
|
|
anchors.fill: parent
|
|
orientation: Qt.Horizontal
|
|
EditorView {
|
|
id: editorView
|
|
vertexTabText: "Vertex Shader"
|
|
fragmentTabText: "Fragment Shader"
|
|
SplitView.preferredWidth: window.width * 0.5
|
|
SplitView.fillWidth: true
|
|
materialAdapter: materialAdapter
|
|
instanceEntry: preview.instanceEntry
|
|
targetModel: preview.modelInstance
|
|
}
|
|
Preview {
|
|
id: preview
|
|
implicitWidth: parent.width * 0.5
|
|
currentMaterial: materialAdapter.material
|
|
}
|
|
}
|
|
|
|
function outputLine(lineText) {
|
|
// Prepend
|
|
editorView.outputTextItem.text = lineText + "\n" + editorView.outputTextItem.text;
|
|
}
|
|
|
|
function printShaderStatusError(stage, msg) {
|
|
let outputString = ""
|
|
outputString += msg.filename + " => " + msg.message
|
|
if (msg.identifier !== null && msg.identifier !== "")
|
|
outputString += " '" + msg.identifier + "'";
|
|
if (msg.line >= 0)
|
|
outputString += ", on line: " + msg.line
|
|
outputLine(outputString)
|
|
}
|
|
|
|
MaterialAdapter {
|
|
id: materialAdapter
|
|
vertexShader: editorView.vertexEditor.text
|
|
fragmentShader: editorView.fragmentEditor.text
|
|
rootNode: preview.rootNode
|
|
uniformModel: editorView.uniformModel
|
|
onVertexStatusChanged: {
|
|
if (vertexStatus.status !== ShaderConstants.Success) {
|
|
editorView.tabBarInfoView.currentIndex = 1
|
|
window.printShaderStatusError(ShaderConstants.Vertex, vertexStatus)
|
|
} else if (fragmentStatus.status === ShaderConstants.Success){
|
|
// both work, clear
|
|
editorView.outputTextItem.text = "";
|
|
}
|
|
}
|
|
onFragmentStatusChanged: {
|
|
if (fragmentStatus.status !== ShaderConstants.Success) {
|
|
editorView.tabBarInfoView.currentIndex = 1
|
|
window.printShaderStatusError(ShaderConstants.Fragment, fragmentStatus)
|
|
} else if (vertexStatus.status === ShaderConstants.Success) {
|
|
// both work, clear
|
|
editorView.outputTextItem.text = "";
|
|
}
|
|
}
|
|
|
|
onVertexShaderChanged: {
|
|
editorView.vertexEditor.text = materialAdapter.vertexShader
|
|
}
|
|
onFragmentShaderChanged: {
|
|
editorView.fragmentEditor.text = materialAdapter.fragmentShader
|
|
}
|
|
}
|
|
}
|