main.qml 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. // Copyright (C) 2023 The Qt Company Ltd.
  2. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
  3. import QtQuick
  4. import QtQuick.Window
  5. import QtQuick.Controls
  6. import QtQuick.Layouts
  7. import QtQuick.Dialogs
  8. import QtCore
  9. import QtQuick3D.MaterialEditor
  10. ApplicationWindow {
  11. id: window
  12. height: 720
  13. width: 1024
  14. visible: true
  15. title: qsTr("Custom Material Editor")
  16. // Context property (see main.cpp)
  17. property url projectFolder: _qtProjectDir // qmllint disable unqualified
  18. Settings {
  19. id: settings
  20. property alias windowX: window.x
  21. property alias windowY: window.y
  22. property alias windowWidth: window.width
  23. property alias windowHeight: window.height
  24. property alias windowVisibility: window.visibility
  25. }
  26. Component.onCompleted: {
  27. mainSplitView.restoreState(settings.value("ui/mainSplitView"))
  28. editorView.restoreState(settings.value("ui/editorView"))
  29. }
  30. Component.onDestruction: {
  31. settings.setValue("ui/mainSplitView", mainSplitView.saveState())
  32. settings.setValue("ui/editorView", editorView.saveState())
  33. }
  34. QtObject {
  35. id: resourceStore
  36. objectName: "QtQuick3DResourceStorePrivate"
  37. }
  38. FileDialog {
  39. id: openMaterialDialog
  40. title: "Open a Material Project File"
  41. nameFilters: [ "Material Editor Project (*.qmp)"]
  42. currentFolder: window.projectFolder
  43. onAccepted: {
  44. if (openMaterialDialog.selectedFile !== null)
  45. materialAdapter.loadMaterial(openMaterialDialog.selectedFile);
  46. }
  47. }
  48. FileDialog {
  49. id: saveAsDialog
  50. fileMode: FileDialog.SaveFile
  51. currentFolder: window.projectFolder
  52. nameFilters: [ "Material Editor Project (*.qmp)"]
  53. onAccepted: materialAdapter.saveMaterial(selectedFile)
  54. }
  55. FileDialog {
  56. id: fragmentShaderImportDialog
  57. title: "Fragment Shader to import"
  58. nameFilters: [ "Fragment Shader (*.frag *.fs *.glsl)" ]
  59. currentFolder: window.projectFolder
  60. onAccepted: {
  61. if (fragmentShaderImportDialog.selectedFile !== null) {
  62. materialAdapter.importFragmentShader(fragmentShaderImportDialog.selectedFile)
  63. }
  64. }
  65. }
  66. FileDialog {
  67. id: vertexShaderImportDialog
  68. title: "Vertex Shader to import"
  69. nameFilters: [ "Vertex Shader (*.vert *.vs *.glsl)" ]
  70. currentFolder: window.projectFolder
  71. onAccepted: {
  72. if (vertexShaderImportDialog.selectedFile !== null) {
  73. materialAdapter.importVertexShader(vertexShaderImportDialog.selectedFile)
  74. }
  75. }
  76. }
  77. FileDialog {
  78. id: saveCompFileDialog
  79. title: "Choose file"
  80. nameFilters: [ "QML Componen (*.qml)" ]
  81. fileMode: FileDialog.SaveFile
  82. currentFolder: window.projectFolder
  83. onAccepted: {
  84. if (selectedFile !== null)
  85. componentFilePath.text = selectedFile
  86. }
  87. }
  88. RegularExpressionValidator {
  89. id: nameValidator
  90. regularExpression: /[a-zA-Z0-9_-]*/
  91. }
  92. Dialog {
  93. id: exportMaterialDialog
  94. title: "Export material"
  95. anchors.centerIn: parent
  96. ColumnLayout {
  97. id: exportFiles
  98. anchors.fill: parent
  99. spacing: 1
  100. RowLayout {
  101. Text {
  102. text: qsTr("Component")
  103. color: palette.text
  104. }
  105. TextField {
  106. id: componentFilePath
  107. readOnly: true
  108. }
  109. Button {
  110. text: qsTr("Choose...")
  111. onClicked: {
  112. saveCompFileDialog.open()
  113. exportMaterialDialog.aboutToHide()
  114. }
  115. }
  116. }
  117. RowLayout {
  118. Text {
  119. text: qsTr("Vertex:")
  120. color: palette.text
  121. }
  122. TextField {
  123. id: vertexFilename
  124. enabled: (editorView.vertexEditor.text !== "")
  125. validator: nameValidator
  126. }
  127. }
  128. RowLayout {
  129. Text {
  130. text: qsTr("Fragment:")
  131. color: palette.text
  132. }
  133. TextField {
  134. id: fragmentFilename
  135. enabled: (editorView.fragmentEditor.text !== "")
  136. validator: nameValidator
  137. }
  138. }
  139. DialogButtonBox {
  140. Button {
  141. text: qsTr("Export")
  142. enabled: (componentFilePath.text !== "" && (!vertexFilename.enabled || (vertexFilename.enabled && vertexFilename.text !== "")) && (!fragmentFilename.enabled || (fragmentFilename.enabled && fragmentFilename.text !== "")))
  143. DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
  144. onClicked: exportMaterialDialog.accept()
  145. }
  146. Button {
  147. text: qsTr("Cancel")
  148. DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole
  149. onClicked: exportMaterialDialog.reject()
  150. }
  151. }
  152. }
  153. onAccepted: {
  154. materialAdapter.exportQmlComponent(componentFilePath.text, vertexFilename.text, fragmentFilename.text)
  155. }
  156. }
  157. SaveChangesDialog {
  158. id: saveChangesDialog
  159. materialAdapter: materialAdapter
  160. saveAsDialog: saveAsDialog
  161. anchors.centerIn: parent
  162. }
  163. AboutDialog {
  164. id: aboutDialog
  165. parent: Overlay.overlay
  166. anchors.centerIn: parent
  167. }
  168. function saveAction() {
  169. // 1. No file name(s) given (call saveAs)
  170. let materialSaveFileUrl = new URL(materialAdapter.materialSaveFile)
  171. if (materialSaveFileUrl.toString().length > 0)
  172. materialAdapter.save()
  173. else
  174. saveAsAction()
  175. }
  176. function openAction() {
  177. openMaterialDialog.open()
  178. }
  179. function newAction() {
  180. saveChangesDialog.doIfChangesSavedOrDiscarded(() => { materialAdapter.reset() });
  181. materialAdapter.reset()
  182. }
  183. function saveAsAction() {
  184. saveAsDialog.open()
  185. }
  186. function quitAction() {
  187. Qt.quit()
  188. }
  189. function aboutAction() {
  190. aboutDialog.open()
  191. }
  192. function importFragmentShader() {
  193. fragmentShaderImportDialog.open()
  194. }
  195. function importVertexShader() {
  196. vertexShaderImportDialog.open()
  197. }
  198. function exportMaterial() {
  199. exportMaterialDialog.open()
  200. }
  201. menuBar: MenuBar {
  202. Menu {
  203. title: qsTr("&File")
  204. Action { text: qsTr("&New..."); onTriggered: window.newAction(); }
  205. Action { text: qsTr("&Open..."); onTriggered: window.openAction(); }
  206. Action { text: qsTr("&Save"); onTriggered: window.saveAction(); }
  207. Action { text: qsTr("Save &As..."); onTriggered: window.saveAsAction(); }
  208. MenuSeparator { }
  209. Menu {
  210. title: qsTr("Import")
  211. Action { text: qsTr("Fragment Shader"); onTriggered: window.importFragmentShader(); }
  212. Action { text: qsTr("Vertex Shader"); onTriggered: window.importVertexShader(); }
  213. }
  214. Action { text: qsTr("Export"); onTriggered: window.exportMaterial(); }
  215. MenuSeparator { }
  216. Action { text: qsTr("&Quit"); onTriggered: window.quitAction(); }
  217. }
  218. Menu {
  219. title: qsTr("&Help")
  220. Action { text: qsTr("&About"); onTriggered: window.aboutAction(); }
  221. }
  222. }
  223. SplitView {
  224. id: mainSplitView
  225. anchors.fill: parent
  226. orientation: Qt.Horizontal
  227. EditorView {
  228. id: editorView
  229. vertexTabText: "Vertex Shader"
  230. fragmentTabText: "Fragment Shader"
  231. SplitView.preferredWidth: window.width * 0.5
  232. SplitView.fillWidth: true
  233. materialAdapter: materialAdapter
  234. instanceEntry: preview.instanceEntry
  235. targetModel: preview.modelInstance
  236. }
  237. Preview {
  238. id: preview
  239. implicitWidth: parent.width * 0.5
  240. currentMaterial: materialAdapter.material
  241. }
  242. }
  243. function outputLine(lineText) {
  244. // Prepend
  245. editorView.outputTextItem.text = lineText + "\n" + editorView.outputTextItem.text;
  246. }
  247. function printShaderStatusError(stage, msg) {
  248. let outputString = ""
  249. outputString += msg.filename + " => " + msg.message
  250. if (msg.identifier !== null && msg.identifier !== "")
  251. outputString += " '" + msg.identifier + "'";
  252. if (msg.line >= 0)
  253. outputString += ", on line: " + msg.line
  254. outputLine(outputString)
  255. }
  256. MaterialAdapter {
  257. id: materialAdapter
  258. vertexShader: editorView.vertexEditor.text
  259. fragmentShader: editorView.fragmentEditor.text
  260. rootNode: preview.rootNode
  261. uniformModel: editorView.uniformModel
  262. onVertexStatusChanged: {
  263. if (vertexStatus.status !== ShaderConstants.Success) {
  264. editorView.tabBarInfoView.currentIndex = 1
  265. window.printShaderStatusError(ShaderConstants.Vertex, vertexStatus)
  266. } else if (fragmentStatus.status === ShaderConstants.Success){
  267. // both work, clear
  268. editorView.outputTextItem.text = "";
  269. }
  270. }
  271. onFragmentStatusChanged: {
  272. if (fragmentStatus.status !== ShaderConstants.Success) {
  273. editorView.tabBarInfoView.currentIndex = 1
  274. window.printShaderStatusError(ShaderConstants.Fragment, fragmentStatus)
  275. } else if (vertexStatus.status === ShaderConstants.Success) {
  276. // both work, clear
  277. editorView.outputTextItem.text = "";
  278. }
  279. }
  280. onVertexShaderChanged: {
  281. editorView.vertexEditor.text = materialAdapter.vertexShader
  282. }
  283. onFragmentShaderChanged: {
  284. editorView.fragmentEditor.text = materialAdapter.fragmentShader
  285. }
  286. }
  287. }