Улучшен дизайн кнопок SpinBox: добавлены современные стили, улучшена визуальная обратная связь, добавлены SVG иконки, оптимизированы размеры и отступы, улучшена читаемость значени

This commit is contained in:
kromskii2 committed 2025-03-14 19:25:58 +07:00
1 parent 2b9165771b
commit 29dce906c5
6110 files changed
+775979

No files matched your search

@@ -0,0 +1,32 @@
// 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.Layouts
import QtQuick.Controls
import QtQuick.Window
Dialog {
id: root
title: qsTr("About Material Editor")
modal: true
dim: false
focus: true
standardButtons: Dialog.Ok
width: Math.max(implicitWidth, 340)
ColumnLayout {
spacing: 12
Label {
text: qsTr("Material Editor %1").arg(Qt.application.version)
font.bold: true
font.pixelSize: Application.font.pixelSize * 1.1
Layout.fillWidth: true
}
Label {
text: qsTr("Copyright (C) 2023 The Qt Company Ltd.")
}
}
}
@@ -0,0 +1,137 @@
// 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 QtQuick3D.MaterialEditor
SplitView {
id: editorView
orientation: Qt.Vertical
property alias vertexEditor: vertEdit
property alias fragmentEditor: fragEdit
property alias outputTextItem: outputTextItem
property alias outputView: outputView
property alias vertexTabText: vertexTabText.text
property alias fragmentTabText: fragTabtext.text
property alias infoStack: infoStack
property alias tabBarInfoView: tabBarInfoView
property alias tabButtonShaderOutput: tabButtonShaderOutput
property alias uniformModel: uniformManagerPane.uniformModel
required property MaterialAdapter materialAdapter
ColumnLayout {
SplitView.preferredHeight: parent.height * .8
TabBar {
id: tabBarEditors
Layout.fillWidth: true
readonly property string defVertText: qsTr("Vertex")
readonly property string defFragText: qsTr("Fragment")
TabButton {
id: vertexTabText
onTextChanged: {
if (text === "")
text = tabBarEditors.defVertText
}
}
TabButton {
id: fragTabtext
onTextChanged: {
if (text === "")
text = tabBarEditors.defFragText
}
}
TabButton {
id: propertiesTabText
text: qsTr("Material Properties")
}
}
// Editors
StackLayout {
id: editorStack
currentIndex: tabBarEditors.currentIndex
Layout.fillWidth: true
ShaderEditor {
id: vertEdit
Layout.fillHeight: true
Layout.fillWidth: true
}
ShaderEditor {
id: fragEdit
Layout.fillHeight: true
Layout.fillWidth: true
}
MaterialPropertiesPane {
id: propertiesPane
targetMaterial: editorView.materialAdapter
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}
ColumnLayout {
spacing: 0
TabBar {
id: tabBarInfoView
Layout.fillWidth: true
TabButton {
id: tabButtonUniforms
text: qsTr("Uniforms")
}
TabButton {
id: tabButtonShaderOutput
text: qsTr("Shader Output")
}
}
// Uniform, compile output etc.
StackLayout {
id: infoStack
currentIndex: tabBarInfoView.currentIndex
// Layout.preferredHeight: parent.height * .2
Layout.fillWidth: true
UniformManagerPane {
id: uniformManagerPane
materialAdapter: editorView.materialAdapter
Layout.fillHeight: true
Layout.fillWidth: true
}
Rectangle {
id: outputView
Layout.fillHeight: true
Layout.fillWidth: true
color: palette.base
ScrollView {
anchors.fill: parent
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
TextArea {
id: outputTextItem
width: outputView.width
padding: 2
color: palette.text
wrapMode: Text.WordWrap
readOnly: true
text: " "
}
}
Button {
anchors.right: parent.right
anchors.rightMargin: 25
anchors.bottom: parent.bottom
anchors.bottomMargin: 5
text: qsTr("Clear")
onClicked: {
outputTextItem.text = "";
}
}
}
}
}
}
@@ -0,0 +1,51 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick
Item {
id: root
required property Item backgroundItem
property alias range: glassEffect.range
property alias blur: glassEffect.blur
property alias color: glassEffect.color
property alias backgroundRect: backgroundSourceImage.sourceRect
ShaderEffectSource {
anchors.fill: parent
id: backgroundSourceImage
sourceRect: Qt.rect(0, 0, width, height)
sourceItem: root.backgroundItem
visible: false
}
ShaderEffectSource {
anchors.fill: parent
id: noiseImageSource
sourceRect: Qt.rect(0, 0, width, height)
sourceItem: noiseImage
visible: false
}
Image {
anchors.fill: parent
id: noiseImage
fillMode: Image.Tile
horizontalAlignment: Image.AlignLeft
verticalAlignment: Image.AlignTop
visible: false
source: "assets/images/noise.png"
}
ShaderEffect {
id: glassEffect
property variant sourceTex: backgroundSourceImage
property variant noiseTex: noiseImageSource
property real range: 0.25;
property real blur: 0.05;
property color color: "white"
anchors.fill: parent
fragmentShader: "assets/shaders/frostedGlass.frag.qsb"
}
}
@@ -0,0 +1,138 @@
// Copyright (C) 2021 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.Controls
import QtQuick.Layouts
import QtQuick3D
import QtQuick3D.MaterialEditor
Pane {
id: root
required property MaterialAdapter targetMaterial
ColumnLayout {
RowLayout {
Label {
text: qsTr("Source Blend")
Layout.fillWidth: true
}
ComboBox {
id: sourceBlendComboBox
textRole: "text"
valueRole: "value"
implicitContentWidthPolicy: ComboBox.WidestText
onActivated: root.targetMaterial.sourceBlend = currentValue
Component.onCompleted: currentIndex = indexOfValue(root.targetMaterial.sourceBlend)
model: [
{ value: CustomMaterial.NoBlend, text: qsTr("No Blend") },
{ value: CustomMaterial.Zero, text: qsTr("Zero") },
{ value: CustomMaterial.One, text: qsTr("One") },
{ value: CustomMaterial.SrcColor, text: qsTr("Source Color") },
{ value: CustomMaterial.OneMinusSrcColor, text: qsTr("1 - Source Color") },
{ value: CustomMaterial.DstColor, text: qsTr("Destination Color") },
{ value: CustomMaterial.OneMinusDstColor, text: qsTr("1 - Destination Color") },
{ value: CustomMaterial.SrcAlpha, text: qsTr("Source Alpha") },
{ value: CustomMaterial.OneMinusSrcAlpha, text: qsTr("1 - Source Alpha") },
{ value: CustomMaterial.DstAlpha, text: qsTr("Destination Alpha") },
{ value: CustomMaterial.OneMinusDstAlpha, text: qsTr("1 - Destination Alpha") },
{ value: CustomMaterial.ConstantColor, text: qsTr("Constant Color") },
{ value: CustomMaterial.OneMinusConstantColor, text: qsTr("1 - Constant Color") },
{ value: CustomMaterial.ConstantAlpha, text: qsTr("Constant Alpha") },
{ value: CustomMaterial.OneMinusConstantAlpha, text: qsTr("1 - Constant Alpha") },
{ value: CustomMaterial.SrcAlphaSaturate, text: qsTr("Source Alpha Saturate") }
]
}
}
RowLayout {
Label {
text: qsTr("Destination Blend")
Layout.fillWidth: true
}
ComboBox {
id: destinationBlendComboBox
textRole: "text"
valueRole: "value"
implicitContentWidthPolicy: ComboBox.WidestText
onActivated: root.targetMaterial.destinationBlend = currentValue
Component.onCompleted: currentIndex = indexOfValue(root.targetMaterial.destinationBlend)
model: [
{ value: CustomMaterial.NoBlend, text: qsTr("No Blend") },
{ value: CustomMaterial.Zero, text: qsTr("Zero") },
{ value: CustomMaterial.One, text: qsTr("One") },
{ value: CustomMaterial.SrcColor, text: qsTr("Source Color") },
{ value: CustomMaterial.OneMinusSrcColor, text: qsTr("1 - Source Color") },
{ value: CustomMaterial.DstColor, text: qsTr("Destination Color") },
{ value: CustomMaterial.OneMinusDstColor, text: qsTr("1 - Destination Color") },
{ value: CustomMaterial.SrcAlpha, text: qsTr("Source Alpha") },
{ value: CustomMaterial.OneMinusSrcAlpha, text: qsTr("1 - Source Alpha") },
{ value: CustomMaterial.DstAlpha, text: qsTr("Destination Alpha") },
{ value: CustomMaterial.OneMinusDstAlpha, text: qsTr("1 - Destination Alpha") },
{ value: CustomMaterial.ConstantColor, text: qsTr("Constant Color") },
{ value: CustomMaterial.OneMinusConstantColor, text: qsTr("1 - Constant Color") },
{ value: CustomMaterial.ConstantAlpha, text: qsTr("Constant Alpha") },
{ value: CustomMaterial.OneMinusConstantAlpha, text: qsTr("1 - Constant Alpha") },
{ value: CustomMaterial.SrcAlphaSaturate, text: qsTr("Source Alpha Saturate") }
]
}
}
RowLayout {
Label {
text: qsTr("Cull Mode")
Layout.fillWidth: true
}
ComboBox {
id: cullModeComboBox
textRole: "text"
valueRole: "value"
implicitContentWidthPolicy: ComboBox.WidestText
onActivated: root.targetMaterial.cullMode = currentValue
Component.onCompleted: currentIndex = indexOfValue(root.targetMaterial.cullMode)
model: [
{ value: CustomMaterial.BackFaceCulling, text: qsTr("Back Face Culling") },
{ value: CustomMaterial.FrontFaceCulling, text: qsTr("Front Face Culling") },
{ value: CustomMaterial.NoCulling, text: qsTr("No Culling") }
]
}
}
RowLayout {
Label {
text: qsTr("Depth Draw Mode")
Layout.fillWidth: true
}
ComboBox {
id: depthDrawModeComboBox
textRole: "text"
valueRole: "value"
implicitContentWidthPolicy: ComboBox.WidestText
onActivated: root.targetMaterial.depthDrawMode = currentValue
Component.onCompleted: currentIndex = indexOfValue(root.targetMaterial.depthDrawMode)
model: [
{ value: CustomMaterial.OpaqueOnlyDepthDraw, text: qsTr("Opaque Only") },
{ value: CustomMaterial.AlwaysDepthDraw, text: qsTr("Always") },
{ value: CustomMaterial.NeverDepthDraw, text: qsTr("Never") },
{ value: CustomMaterial.OpaquePrePassDepthDraw, text: qsTr("Opaque Pre-pass") }
]
}
}
RowLayout {
Label {
text: qsTr("Shading Mode")
Layout.fillWidth: true
}
ComboBox {
id: shadingModeComboBox
textRole: "text"
valueRole: "value"
implicitContentWidthPolicy: ComboBox.WidestText
onActivated: root.targetMaterial.shadingMode = currentValue
Component.onCompleted: currentIndex = indexOfValue(root.targetMaterial.shadingMode)
model: [
{ value: CustomMaterial.Shaded, text: qsTr("Shaded") },
{ value: CustomMaterial.Unshaded, text: qsTr("Unshaded") }
]
}
}
}
}
@@ -0,0 +1,88 @@
// 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 QtCore
import QtQuick3D
import QtQuick3D.Helpers
Item {
id: previewRoot
property url skyBoxTexturePath: "assets/skybox/OpenfootageNET_lowerAustria01-1024.hdr"
property CustomMaterial currentMaterial: CustomMaterial {
}
property PrincipledMaterial fallbackMaterial: PrincipledMaterial {
baseColor: "magenta"
}
property alias modelInstance: model
property alias rootNode: resourceRoot
Settings {
property alias cameraOriginRotation: originNode.rotation
property alias cameraRotation: sceneCamera.rotation
property alias cameraPosition: sceneCamera.position
}
View3D {
id: view
anchors.fill: parent
environment: SceneEnvironment {
id: sceneEnvironment
backgroundMode: previewControls.enableIBL ? SceneEnvironment.SkyBox : SceneEnvironment.Transparent
lightProbe: previewControls.enableIBL ? skyboxTexture : null
}
Texture {
id: skyboxTexture
source: previewRoot.skyBoxTexturePath
}
Node {
id: resourceRoot
}
property alias cameraOrigin: originNode
Node {
id: originNode
PerspectiveCamera {
id: sceneCamera
z: 300
}
}
camera: sceneCamera
DirectionalLight {
id: light
z: 600
eulerRotation: Qt.vector3d(30, 0, 0)
visible: previewControls.enableDirectionalLight
}
Model {
id: model
source: previewControls.modelSource
materials: [ previewRoot.currentMaterial, previewRoot.fallbackMaterial ]
}
OrbitCameraController {
id: cameraController
origin: originNode
camera: sceneCamera
panEnabled: false
}
}
PreviewControls {
id: previewControls
width: parent.width
targetView: view
orbitCamera: cameraController
}
}
@@ -0,0 +1,117 @@
// 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 QtCore
import QtQuick3D
import QtQuick3D.Helpers
Item {
id: previewControls
required property View3D targetView
required property OrbitCameraController orbitCamera
property alias modelSource: modelComboBox.currentValue
property alias enableIBL: iblEnableButton.checked
property alias enableDirectionalLight: directionalLightEnabledButton.checked
Settings {
property alias enableIbl: previewControls.enableIBL
property alias enableDirectionalLight: previewControls.enableDirectionalLight
property alias environmentOrientationSliderValue: environmentOrientationSlider.value
}
FrostedGlass {
width: parent.width
height: layout.implicitHeight
backgroundItem: previewControls.targetView
backgroundRect: Qt.rect(0, 0, width, height)
// range: 0.05
// blur: 0.005
range: 0.05
blur: 0.05
//color: "pink"
}
RowLayout {
id: layout
anchors.left: parent.left
anchors.leftMargin: 10
Label {
text: "Model"
}
ComboBox {
id: modelComboBox
textRole: "text"
valueRole: "value"
model: ListModel {
ListElement {
text: "Sphere"
value: "#Sphere"
}
ListElement {
text: "Cube"
value: "#Cube"
}
ListElement {
text: "Plane"
value: "#Rectangle"
}
ListElement {
text: "Suzanne"
value: "assets/meshes/suzanne.mesh"
}
}
}
Button {
text: "Reset View"
onClicked: {
previewControls.orbitCamera.origin.rotation = Qt.quaternion(1, 0, 0, 0)
previewControls.orbitCamera.camera.rotation = Qt.quaternion(1, 0, 0, 0)
previewControls.orbitCamera.camera.position = Qt.vector3d(0, 0, 300)
environmentOrientationSlider.value = 0
}
}
ToolButton {
id: iblEnableButton
icon.source: "assets/icons/texture.png"
checkable: true
checked: true
hoverEnabled: true
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: qsTr("Toggle the use of IBL")
}
Label {
visible: previewControls.enableIBL
text: "Environment Orientation"
}
Slider {
visible: previewControls.enableIBL
id: environmentOrientationSlider
Layout.fillWidth: true
from: -180
to: 180
value: 0
onValueChanged: {
previewControls.targetView.environment.probeOrientation = Qt.vector3d(0, value, 0)
}
}
ToolButton {
id: directionalLightEnabledButton
icon.source: "assets/icons/lightdirectional.png"
checkable: true
checked: true
hoverEnabled: true
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: qsTr("Toggle a Directional Light")
}
}
}
@@ -0,0 +1,114 @@
// Copyright (C) 2021 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.Controls
import QtQuick3D.MaterialEditor
Dialog {
id: root
title: qsTr("Unsaved changes")
modal: true
required property MaterialAdapter materialAdapter
required property var saveAsDialog
function doIfChangesSavedOrDiscarded(actionFunction) {
if (!materialAdapter.unsavedChanges) {
actionFunction()
return
}
// There are unsaved changes, so we need to prompt.
function disconnectSaveChangesSignals() {
root.accepted.disconnect(saveChanges)
root.discarded.disconnect(discardChanges)
root.rejected.disconnect(cancel)
}
function saveChanges() {
if (materialAdapter.materialSaveFile.toString().length > 0) {
// Existing project; can save without a dialog.
if (materialAdapter.save()) {
// Saved successfully, so now we can perform the action.
performAction()
} else {
// Failed to save; cancel.
cancel()
}
} else {
// New project; need to save as.
function disconnectSaveAsSignals() {
materialAdapter.errorOccurred.disconnect(saveAsFailed)
materialAdapter.postMaterialSaved.disconnect(saveAsSucceeded)
saveAsDialog.rejected.disconnect(saveAsDialogRejected)
}
function saveAsSucceeded() {
disconnectSaveAsSignals()
performAction()
}
function saveAsFailed() {
disconnectSaveAsSignals()
disconnectSaveChangesSignals()
}
function saveAsDialogRejected() {
disconnectSaveAsSignals()
cancel()
}
materialAdapter.errorOccurred.connect(saveAsFailed)
materialAdapter.postMaterialSaved.connect(saveAsSucceeded)
saveAsDialog.rejected.connect(saveAsDialogRejected)
saveAsDialog.open()
}
}
function discardChanges() {
performAction()
root.close()
}
function performAction() {
disconnectSaveChangesSignals()
actionFunction()
}
function cancel() {
disconnectSaveChangesSignals()
}
root.accepted.connect(saveChanges)
root.discarded.connect(discardChanges)
root.rejected.connect(cancel)
root.open()
}
Label {
text: qsTr("Save changes to the material before closing?")
}
// Using a DialogButtonBox allows us to assign objectNames to the buttons,
// which makes it possible to test them.
footer: DialogButtonBox {
Button {
objectName: "cancelDialogButton"
text: qsTr("Cancel")
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
}
Button {
objectName: "saveChangesDialogButton"
text: qsTr("Save")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
Button {
objectName: "discardChangesDialogButton"
text: qsTr("Don't save")
DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole
}
}
}
@@ -0,0 +1,107 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick3D.MaterialEditor
Flickable {
id: flickable
property alias font: textArea.font
property alias text: textArea.text
property alias textDocument: textArea.textDocument
property alias lineColumn: lineNumbers
property alias textArea: textArea
property int rowHeight: Math.ceil(fontMetrics.lineSpacing)
property int marginsTop: 10
property int marginsLeft: 4
property int lineCountWidth: 40
clip: true
boundsBehavior: Flickable.StopAtBounds
ScrollBar.vertical: ScrollBar {
width: 15
active: true
}
ScrollBar.horizontal: ScrollBar {
width: 15
active: true
}
FontMetrics {
id: fontMetrics
font: textArea.font
}
Column {
id: lineNumbers
anchors.left: parent.left
anchors.leftMargin: flickable.marginsLeft
anchors.topMargin: flickable.marginsTop
y: flickable.marginsTop
width: flickable.lineCountWidth
function labelAt(lineNr) {
if (lineNr > 0) {
if (lineNr > repeater.count)
lineNr = repeater.count; // Best guess at this point...
return repeater.itemAt(lineNr - 1);
}
return null;
}
function range(start, end) {
var rangeArray = new Array(end-start);
for (var i = 0; i < rangeArray.length; i++)
rangeArray[i] = start+i;
return rangeArray;
}
Repeater {
id: repeater
model: textArea.lineCount
delegate: Label {
required property int index
font: textArea.font
width: parent.width
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
height: flickable.rowHeight
renderType: Text.NativeRendering
text: index+1
}
}
}
Rectangle {
id: lineNumbersSeperator
y: 4
height: parent.height
anchors.left: lineNumbers.right
anchors.leftMargin: flickable.marginsLeft
width: 1
color: "#ddd"
}
SyntaxHighlighter {
id: syntaxHighlighter
document: textArea.textDocument
}
TextArea.flickable: TextArea {
id: textArea
textFormat: Qt.PlainText
focus: false
selectByMouse: true
leftPadding: flickable.marginsLeft
rightPadding: flickable.marginsLeft
topPadding: flickable.marginsTop
bottomPadding: flickable.marginsTop
tabStopDistance: fontMetrics.averageCharacterWidth * 4;
anchors.left: lineNumbersSeperator.right
}
}
@@ -0,0 +1,737 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import QtQuick3D.MaterialEditor
Pane {
id: uniformManagerPane
property alias uniformModel: uniformModel
required property MaterialAdapter materialAdapter
SplitView {
anchors.fill: parent
ColumnLayout {
clip: true
RowLayout {
id: tableControls
function insertUniform() {
let rowCount = uniformManagerPane.materialAdapter.uniformModel.rowCount;
if (uniformManagerPane.materialAdapter.uniformModel.insertRow(rowCount, typeComboBox.currentIndex, uniformNameTextInput.text))
uniformNameTextInput.text = ""
}
Label {
text: "Type:"
}
ComboBox {
id: typeComboBox
textRole: "text"
valueRole: "value"
model: [
{ value: UniformModel.Bool, text: "bool" },
{ value: UniformModel.Int, text: "int" },
{ value: UniformModel.Float, text: "float" },
{ value: UniformModel.Vec2, text: "vec2" },
{ value: UniformModel.Vec3, text: "vec3" },
{ value: UniformModel.Vec4, text: "vec4" },
{ value: UniformModel.Mat44, text: "mat44" },
{ value: UniformModel.Sampler, text: "sampler" }
]
}
TextField {
id: uniformNameTextInput
validator: RegularExpressionValidator {
regularExpression: /[a-zA-Z_][a-zA-Z0-9_]+/
}
Layout.fillWidth: true
placeholderText: "Uniform Name"
onAccepted: tableControls.insertUniform()
}
Button {
id: addButton
text: "Add"
enabled: uniformNameTextInput.text != ""
onClicked: tableControls.insertUniform()
}
}
// Column Header
Row {
id: columnsHeader
Layout.fillWidth: true
Label {
width: uniformTable.columnWidth(0)
text: "Type"
verticalAlignment: Text.AlignVCenter
}
Label {
width: uniformTable.columnWidth(1)
text: "Name"
verticalAlignment: Text.AlignVCenter
}
Label {
width: uniformTable.columnWidth(2)
text: "Value"
verticalAlignment: Text.AlignVCenter
}
}
ListView {
id: uniformTable
Layout.fillHeight: true
Layout.fillWidth: true
flickableDirection: Flickable.VerticalFlick
model: UniformModel {
id: uniformModel
}
clip: true
ScrollBar.vertical: ScrollBar { }
highlight: Rectangle {
color: palette.highlight
}
property var typeStrings: [
"bool",
"int",
"float",
"vec2",
"vec3",
"vec4",
"mat44",
"sampler"
]
function convertValueToString(value, type)
{
if (type === 0) {
// bool
return String(value);
} if (type === 1) {
// int
return String(value);
} if (type === 2) {
// float
return String(value);
} if (type === 3) {
// vec2
return "(" + value.x + ", " + value.y + ")"
} if (type === 4) {
// vec3
return "(" + value.x + ", " + value.y + ", " + value.z + ")"
} if (type === 5) {
// vec4
return "(" + value.x + ", " + value.y + ", " + value.z + ", " + value.w + ")"
} if (type === 6) {
// mat44
return value.toString()
} if (type === 7) {
// sampler
return "[Texture]"
}
}
function columnWidth(column) {
if (column === 0)
return 50;
if (column === 1)
return 100;
return 100;
}
delegate: Item {
id: delegateRoot
required property int type
required property string name
required property var value
required property int index
width: ListView.view.width
height: typeLabel.implicitHeight
Row {
Label {
id: typeLabel
width: uniformTable.columnWidth(0)
text: uniformTable.typeStrings[delegateRoot.type]
}
Label {
width: uniformTable.columnWidth(1)
text: delegateRoot.name
}
Label {
width: uniformTable.columnWidth(2)
Layout.fillWidth: true
text: uniformTable.convertValueToString(delegateRoot.value, delegateRoot.type)
}
}
MouseArea {
anchors.fill: parent
onClicked: {
uniformTable.currentIndex = delegateRoot.index
}
}
}
}
}
Item {
id: uniformValueEditor
width: parent.width * 0.5
clip: true
Label {
id: emptyLabel
visible: uniformTable.currentIndex == -1
anchors.centerIn: parent
text: "Select a uniform to edit"
}
Repeater {
id: delegateRepeater
anchors.fill: parent
model: uniformModel
Item {
id: editorRoot
required property int index
required property int type
required property string name
required property var model
anchors.fill: parent
anchors.margins: 10
visible: index === uniformTable.currentIndex
Item {
id: header
width: parent.width
anchors.top: parent.top
height: removeButton.implicitHeight
RowLayout {
anchors.fill: parent
id: headerLayout
Label {
text: "Uniform: " + editorRoot.name
Layout.fillWidth: true
elide: Text.ElideRight
}
Button {
id: removeButton
text: "Remove"
Layout.alignment: Qt.AlignRight
onClicked: {
uniformManagerPane.materialAdapter.uniformModel.removeRow(uniformTable.currentIndex, 1)
}
}
}
}
Loader {
id: editorLoader
anchors.top: header.bottom
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
sourceComponent: editors[editorRoot.type]
readonly property list<Component> editors: [
boolEditor,
intEditor,
floatEditor,
vec2Editor,
vec3Editor,
vec4Editor,
mat44Editor,
samplerEditor
]
Component {
id: boolEditor
CheckBox {
text: "value"
checked: editorRoot.model.value
onCheckedChanged: editorRoot.model.value = checked
}
}
Component {
id: intEditor
TextField {
text: editorRoot.model.value
validator: IntValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = parseInt(text)
}
}
}
Component {
id: floatEditor
ColumnLayout {
TextField {
Layout.fillWidth: true
text: editorRoot.model.value
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput) {
var floatValue = parseFloat(text);
floatSlider.updateMinMax(floatValue);
editorRoot.value = floatValue;
}
}
}
Slider {
id: floatSlider
// Grow slider min & max based on given values
function updateMinMax(newValue) {
if (from > newValue)
from = newValue;
if (to < newValue)
to = newValue;
value = newValue;
}
from: 0.0
to: 1.0
onValueChanged: {
editorRoot.model.value = value;
}
Component.onCompleted: {
updateMinMax(editorRoot.model.value);
}
}
}
}
Component {
id: vec2Editor
ColumnLayout {
RowLayout {
Label {
text: "X:"
}
TextField {
id: xField
text: editorRoot.model.value.x
validator: DoubleValidator {
locale: "C"
}
onEditingFinished: {
if (acceptableInput)
editorRoot.model.value = Qt.vector2d(parseFloat(text), editorRoot.model.value.y)
}
}
}
RowLayout {
Label {
text: "Y:"
}
TextField {
id: yField
text: editorRoot.model.value.y
validator: DoubleValidator {
locale: "C"
}
onEditingFinished: {
if (acceptableInput)
editorRoot.model.value = Qt.vector2d(editorRoot.model.value.x, parseFloat(text))
}
}
}
}
}
Component {
id: vec3Editor
ColumnLayout {
RowLayout {
Label {
text: "X:"
}
TextField {
id: xField
text: editorRoot.model.value.x
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.vector3d(parseFloat(text), editorRoot.model.value.y, editorRoot.model.value.z)
}
}
}
RowLayout {
Label {
text: "Y:"
}
TextField {
id: yField
text: editorRoot.model.value.y
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.vector3d(editorRoot.model.value.x, parseFloat(text), editorRoot.model.value.z)
}
}
}
RowLayout {
Label {
text: "Z:"
}
TextField {
id: zField
text: editorRoot.model.value.z
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.vector3d(editorRoot.model.value.x, editorRoot.model.value.y, parseFloat(text))
}
}
}
}
}
Component {
id: vec4Editor
ColumnLayout {
RowLayout {
Label {
text: "X:"
}
TextField {
id: xField
text: editorRoot.model.value.x
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.vector4d(parseFloat(text), editorRoot.model.value.y, editorRoot.model.value.z, editorRoot.model.value.w)
}
}
}
RowLayout {
Label {
text: "Y:"
}
TextField {
id: yField
text: editorRoot.model.value.y
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.vector4d(editorRoot.model.value.x, parseFloat(text), editorRoot.model.value.z, editorRoot.model.value.w)
}
}
}
RowLayout {
Label {
text: "Z:"
}
TextField {
id: zField
text: editorRoot.model.value.z
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.vector4d(editorRoot.model.value.x, editorRoot.model.value.y, parseFloat(text), editorRoot.model.value.w)
}
}
}
RowLayout {
Label {
text: "W:"
}
TextField {
id: wField
text: editorRoot.model.value.w
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.vector4d(editorRoot.model.value.x, editorRoot.model.value.y, editorRoot.model.value.z, parseFloat(text))
}
}
}
}
}
Component {
id: mat44Editor
ColumnLayout {
RowLayout {
TextField {
text: editorRoot.model.value.m11
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.matrix4x4(parseFloat(text), editorRoot.model.value.m12, editorRoot.model.value.m13 , editorRoot.model.value.m14,
editorRoot.model.value.m21, editorRoot.model.value.m22, editorRoot.model.value.m23, editorRoot.model.value.m24,
editorRoot.model.value.m31, editorRoot.model.value.m32, editorRoot.model.value.m33, editorRoot.model.value.m34,
editorRoot.model.value.m41, editorRoot.model.value.m42, editorRoot.model.value.m43, editorRoot.model.value.m44)
}
}
TextField {
text: editorRoot.model.value.m12
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.matrix4x4(editorRoot.model.value.m11, parseFloat(text), editorRoot.model.value.m13 , editorRoot.model.value.m14,
editorRoot.model.value.m21, editorRoot.model.value.m22, editorRoot.model.value.m23, editorRoot.model.value.m24,
editorRoot.model.value.m31, editorRoot.model.value.m32, editorRoot.model.value.m33, editorRoot.model.value.m34,
editorRoot.model.value.m41, editorRoot.model.value.m42, editorRoot.model.value.m43, editorRoot.model.value.m44)
}
}
TextField {
text: editorRoot.model.value.m13
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.matrix4x4(editorRoot.model.value.m11, editorRoot.model.value.m12, parseFloat(text), editorRoot.model.value.m14,
editorRoot.model.value.m21, editorRoot.model.value.m22, editorRoot.model.value.m23, editorRoot.model.value.m24,
editorRoot.model.value.m31, editorRoot.model.value.m32, editorRoot.model.value.m33, editorRoot.model.value.m34,
editorRoot.model.value.m41, editorRoot.model.value.m42, editorRoot.model.value.m43, editorRoot.model.value.m44)
}
}
TextField {
text: editorRoot.model.value.m14
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.matrix4x4(editorRoot.model.value.m11, editorRoot.model.value.m12, editorRoot.model.value.m13, parseFloat(text),
editorRoot.model.value.m21, editorRoot.model.value.m22, editorRoot.model.value.m23, editorRoot.model.value.m24,
editorRoot.model.value.m31, editorRoot.model.value.m32, editorRoot.model.value.m33, editorRoot.model.value.m34,
editorRoot.model.value.m41, editorRoot.model.value.m42, editorRoot.model.value.m43, editorRoot.model.value.m44)
}
}
}
RowLayout {
TextField {
text: editorRoot.model.value.m21
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.matrix4x4(editorRoot.model.value.m11, editorRoot.model.value.m12, editorRoot.model.value.m13, editorRoot.model.value.m14,
parseFloat(text), editorRoot.model.value.m22, editorRoot.model.value.m23, editorRoot.model.value.m24,
editorRoot.model.value.m31, editorRoot.model.value.m32, editorRoot.model.value.m33, editorRoot.model.value.m34,
editorRoot.model.value.m41, editorRoot.model.value.m42, editorRoot.model.value.m43, editorRoot.model.value.m44)
}
}
TextField {
text: editorRoot.model.value.m22
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.matrix4x4(editorRoot.model.value.m11, editorRoot.model.value.m12, editorRoot.model.value.m13, editorRoot.model.value.m14,
editorRoot.model.value.m21, parseFloat(text), editorRoot.model.value.m23, editorRoot.model.value.m24,
editorRoot.model.value.m31, editorRoot.model.value.m32, editorRoot.model.value.m33, editorRoot.model.value.m34,
editorRoot.model.value.m41, editorRoot.model.value.m42, editorRoot.model.value.m43, editorRoot.model.value.m44)
}
}
TextField {
text: editorRoot.model.value.m23
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.matrix4x4(editorRoot.model.value.m11, editorRoot.model.value.m12, editorRoot.model.value.m13, editorRoot.model.value.m14,
editorRoot.model.value.m21, editorRoot.model.value.m22, parseFloat(text), editorRoot.model.value.m24,
editorRoot.model.value.m31, editorRoot.model.value.m32, editorRoot.model.value.m33, editorRoot.model.value.m34,
editorRoot.model.value.m41, editorRoot.model.value.m42, editorRoot.model.value.m43, editorRoot.model.value.m44)
}
}
TextField {
text: editorRoot.model.value.m24
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.matrix4x4(editorRoot.model.value.m11, editorRoot.model.value.m12, editorRoot.model.value.m13, editorRoot.model.value.m14,
editorRoot.model.value.m21, editorRoot.model.value.m22, editorRoot.model.value.m23, parseFloat(text),
editorRoot.model.value.m31, editorRoot.model.value.m32, editorRoot.model.value.m33, editorRoot.model.value.m34,
editorRoot.model.value.m41, editorRoot.model.value.m42, editorRoot.model.value.m43, editorRoot.model.value.m44)
}
}
}
RowLayout {
TextField {
text: editorRoot.model.value.m31
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.matrix4x4(editorRoot.model.value.m11, editorRoot.model.value.m12, editorRoot.model.value.m13, editorRoot.model.value.m14,
editorRoot.model.value.m21, editorRoot.model.value.m22, editorRoot.model.value.m23, editorRoot.model.value.m24,
parseFloat(text), editorRoot.model.value.m32, editorRoot.model.value.m33, editorRoot.model.value.m34,
editorRoot.model.value.m41, editorRoot.model.value.m42, editorRoot.model.value.m43, editorRoot.model.value.m44)
}
}
TextField {
text: editorRoot.model.value.m32
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.matrix4x4(editorRoot.model.value.m11, editorRoot.model.value.m12, editorRoot.model.value.m13, editorRoot.model.value.m14,
editorRoot.model.value.m21, editorRoot.model.value.m22, editorRoot.model.value.m23, editorRoot.model.value.m24,
editorRoot.model.value.m31, parseFloat(text), editorRoot.model.value.m33, editorRoot.model.value.m34,
editorRoot.model.value.m41, editorRoot.model.value.m42, editorRoot.model.value.m43, editorRoot.model.value.m44)
}
}
TextField {
text: editorRoot.model.value.m33
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.matrix4x4(editorRoot.model.value.m11, editorRoot.model.value.m12, editorRoot.model.value.m13, editorRoot.model.value.m14,
editorRoot.model.value.m21, editorRoot.model.value.m22, editorRoot.model.value.m23, editorRoot.model.value.m24,
editorRoot.model.value.m31, editorRoot.model.value.m32, parseFloat(text), editorRoot.model.value.m34,
editorRoot.model.value.m41, editorRoot.model.value.m42, editorRoot.model.value.m43, editorRoot.model.value.m44)
}
}
TextField {
text: editorRoot.model.value.m34
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.matrix4x4(editorRoot.model.value.m11, editorRoot.model.value.m12, editorRoot.model.value.m13, editorRoot.model.value.m14,
editorRoot.model.value.m21, editorRoot.model.value.m22, editorRoot.model.value.m23, editorRoot.model.value.m24,
editorRoot.model.value.m31, editorRoot.model.value.m32, editorRoot.model.value.m33, parseFloat(text),
editorRoot.model.value.m41, editorRoot.model.value.m42, editorRoot.model.value.m43, editorRoot.model.value.m44)
}
}
}
RowLayout {
TextField {
text: editorRoot.model.value.m41
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.matrix4x4(editorRoot.model.value.m11, editorRoot.model.value.m12, editorRoot.model.value.m13, editorRoot.model.value.m14,
editorRoot.model.value.m21, editorRoot.model.value.m22, editorRoot.model.value.m23, editorRoot.model.value.m24,
editorRoot.model.value.m31, editorRoot.model.value.m32, editorRoot.model.value.m33, editorRoot.model.value.m34,
parseFloat(text), editorRoot.model.value.m42, editorRoot.model.value.m43, editorRoot.model.value.m44)
}
}
TextField {
text: editorRoot.model.value.m42
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.matrix4x4(editorRoot.model.value.m11, editorRoot.model.value.m12, editorRoot.model.value.m13, editorRoot.model.value.m14,
editorRoot.model.value.m21, editorRoot.model.value.m22, editorRoot.model.value.m23, editorRoot.model.value.m24,
editorRoot.model.value.m31, editorRoot.model.value.m32, editorRoot.model.value.m33, editorRoot.model.value.m34,
editorRoot.model.value.m41, parseFloat(text), editorRoot.model.value.m43, editorRoot.model.value.m44)
}
}
TextField {
text: editorRoot.model.value.m43
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.matrix4x4(editorRoot.model.value.m11, editorRoot.model.value.m12, editorRoot.model.value.m13, editorRoot.model.value.m14,
editorRoot.model.value.m21, editorRoot.model.value.m22, editorRoot.model.value.m23, editorRoot.model.value.m24,
editorRoot.model.value.m31, editorRoot.model.value.m32, editorRoot.model.value.m33, editorRoot.model.value.m34,
editorRoot.model.value.m41, editorRoot.model.value.m42, parseFloat(text), editorRoot.model.value.m44)
}
}
TextField {
text: editorRoot.model.value.m44
validator: DoubleValidator {
locale: "C"
}
onEditingFinished:{
if (acceptableInput)
editorRoot.model.value = Qt.matrix4x4(editorRoot.model.value.m11, editorRoot.model.value.m12, editorRoot.model.value.m13, editorRoot.model.value.m14,
editorRoot.model.value.m21, editorRoot.model.value.m22, editorRoot.model.value.m23, editorRoot.model.value.m24,
editorRoot.model.value.m31, editorRoot.model.value.m32, editorRoot.model.value.m33, editorRoot.model.value.m34,
editorRoot.model.value.m41, editorRoot.model.value.m42, editorRoot.model.value.m43, parseFloat(text))
}
}
}
}
}
Component {
id: samplerEditor
ColumnLayout {
Image {
id: previewImage
sourceSize.width: 128
sourceSize.height: 128
fillMode: Image.PreserveAspectFit
}
Button {
text: "Choose Image"
onClicked: {
textureSourceDialog.open()
}
}
FileDialog {
id: textureSourceDialog
title: "Open an Image File"
nameFilters: [ uniformManagerPane.materialAdapter.getSupportedImageFormatsFilter()]
onAccepted: {
if (textureSourceDialog.selectedFile !== null) {
editorRoot.model.value = textureSourceDialog.selectedFile
previewImage.source = textureSourceDialog.selectedFile
}
}
}
}
}
}
}
}
}
}
}
@@ -0,0 +1,317 @@
// 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
}
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
}
}
}
@@ -0,0 +1,283 @@
import QtQuick.tooling 1.2
// This file describes the plugin-supplied types contained in the library.
// It is used for QML tooling purposes only.
//
// This file was auto-generated by qmltyperegistrar.
Module {
Component {
file: "materialadapter.h"
name: "MaterialAdapter"
accessSemantics: "reference"
prototype: "QObject"
exports: ["QtQuick3D.MaterialEditor/MaterialAdapter 1.0"]
exportMetaObjectRevisions: [256]
Property {
name: "material"
type: "QQuick3DCustomMaterial"
isPointer: true
read: "material"
notify: "materialChanged"
index: 0
isReadonly: true
}
Property {
name: "rootNode"
type: "QQuick3DNode"
isPointer: true
read: "rootNode"
write: "setRootNode"
notify: "rootNodeChanged"
index: 1
}
Property {
name: "fragmentShader"
type: "QString"
read: "fragmentShader"
write: "setFragmentShader"
notify: "fragmentShaderChanged"
index: 2
}
Property {
name: "vertexShader"
type: "QString"
read: "vertexShader"
write: "setVertexShader"
notify: "vertexShaderChanged"
index: 3
}
Property {
name: "vertexStatus"
type: "ShaderBuildMessage"
read: "vertexStatus"
notify: "vertexStatusChanged"
index: 4
isReadonly: true
}
Property {
name: "fragmentStatus"
type: "ShaderBuildMessage"
read: "fragmentStatus"
notify: "fragmentStatusChanged"
index: 5
isReadonly: true
}
Property {
name: "uniformModel"
type: "UniformModel"
isPointer: true
read: "uniformModel"
write: "setUniformModel"
notify: "uniformModelChanged"
index: 6
}
Property {
name: "unsavedChanges"
type: "bool"
read: "unsavedChanges"
write: "setUnsavedChanges"
notify: "unsavedChangesChanged"
index: 7
}
Property {
name: "materialSaveFile"
type: "QUrl"
read: "materialSaveFile"
write: "setMaterialSaveFile"
notify: "materialSaveFileChanged"
index: 8
}
Property {
name: "cullMode"
type: "QQuick3DMaterial::CullMode"
read: "cullMode"
write: "setCullMode"
notify: "cullModeChanged"
index: 9
}
Property {
name: "depthDrawMode"
type: "QQuick3DMaterial::DepthDrawMode"
read: "depthDrawMode"
write: "setDepthDrawMode"
notify: "depthDrawModeChanged"
index: 10
}
Property {
name: "shadingMode"
type: "QQuick3DCustomMaterial::ShadingMode"
read: "shadingMode"
write: "setShadingMode"
notify: "shadingModeChanged"
index: 11
}
Property {
name: "sourceBlend"
type: "QQuick3DCustomMaterial::BlendMode"
read: "srcBlend"
write: "setSrcBlend"
notify: "srcBlendChanged"
index: 12
}
Property {
name: "destinationBlend"
type: "QQuick3DCustomMaterial::BlendMode"
read: "dstBlend"
write: "setDstBlend"
notify: "dstBlendChanged"
index: 13
}
Signal { name: "materialChanged" }
Signal { name: "fragmentShaderChanged" }
Signal { name: "vertexShaderChanged" }
Signal { name: "vertexStatusChanged" }
Signal { name: "uniformModelChanged" }
Signal { name: "fragmentStatusChanged" }
Signal { name: "unsavedChangesChanged" }
Signal { name: "materialSaveFileChanged" }
Signal { name: "errorOccurred" }
Signal { name: "postMaterialSaved" }
Signal { name: "rootNodeChanged" }
Signal { name: "cullModeChanged" }
Signal { name: "depthDrawModeChanged" }
Signal { name: "shadingModeChanged" }
Signal { name: "srcBlendChanged" }
Signal { name: "dstBlendChanged" }
Method {
name: "importFragmentShader"
Parameter { name: "shaderFile"; type: "QUrl" }
}
Method {
name: "importVertexShader"
Parameter { name: "shaderFile"; type: "QUrl" }
}
Method { name: "save"; type: "bool" }
Method {
name: "saveMaterial"
type: "bool"
Parameter { name: "materialFile"; type: "QUrl" }
}
Method {
name: "loadMaterial"
type: "bool"
Parameter { name: "materialFile"; type: "QUrl" }
}
Method {
name: "exportQmlComponent"
type: "bool"
Parameter { name: "componentFile"; type: "QUrl" }
Parameter { name: "vertName"; type: "QString" }
Parameter { name: "fragName"; type: "QString" }
}
Method { name: "reset" }
Method { name: "getSupportedImageFormatsFilter"; type: "QString" }
}
Component {
file: "qsyntaxhighlighter.h"
name: "QSyntaxHighlighter"
accessSemantics: "reference"
prototype: "QObject"
Method { name: "rehighlight" }
Method {
name: "rehighlightBlock"
Parameter { name: "block"; type: "QTextBlock" }
}
Method {
name: "_q_reformatBlocks"
Parameter { name: "from"; type: "int" }
Parameter { name: "charsRemoved"; type: "int" }
Parameter { name: "charsAdded"; type: "int" }
}
Method { name: "_q_delayedRehighlight" }
}
Component {
file: "buildmessage.h"
name: "ShaderBuildMessage"
accessSemantics: "value"
exports: ["QtQuick3D.MaterialEditor/shaderStatus 1.0"]
isCreatable: false
exportMetaObjectRevisions: [256]
Enum {
name: "Status"
isScoped: true
values: ["Success", "Error"]
}
Enum {
name: "Stage"
isScoped: true
values: ["Vertex", "Fragment"]
}
Property { name: "filename"; type: "QString"; read: "filename"; index: 0; isReadonly: true }
Property { name: "message"; type: "QString"; read: "message"; index: 1; isReadonly: true }
Property { name: "identifier"; type: "QString"; read: "identifier"; index: 2; isReadonly: true }
Property { name: "line"; type: "qlonglong"; read: "line"; index: 3; isReadonly: true }
Property { name: "status"; type: "Status"; read: "status"; index: 4; isReadonly: true }
Property { name: "stage"; type: "Stage"; read: "stage"; index: 5; isReadonly: true }
}
Component {
file: "buildmessage.h"
name: "ShaderBuildMessageDerived"
accessSemantics: "none"
prototype: "ShaderBuildMessage"
exports: ["QtQuick3D.MaterialEditor/ShaderConstants 1.0"]
isCreatable: false
exportMetaObjectRevisions: [256]
}
Component {
file: "syntaxhighlighter.h"
name: "SyntaxHighlighter"
accessSemantics: "reference"
prototype: "QSyntaxHighlighter"
exports: ["QtQuick3D.MaterialEditor/SyntaxHighlighter 1.0"]
exportMetaObjectRevisions: [256]
Property {
name: "document"
type: "QQuickTextDocument"
isPointer: true
read: "document"
write: "setDocument"
notify: "documentChanged"
index: 0
}
Signal { name: "documentChanged" }
}
Component {
file: "uniformmodel.h"
name: "UniformModel"
accessSemantics: "reference"
prototype: "QAbstractListModel"
exports: ["QtQuick3D.MaterialEditor/UniformModel 1.0"]
exportMetaObjectRevisions: [256]
Enum {
name: "UniformType"
values: [
"Bool",
"Int",
"Float",
"Vec2",
"Vec3",
"Vec4",
"Mat44",
"Sampler"
]
}
Method {
name: "insertRow"
type: "bool"
Parameter { name: "rowIndex"; type: "int" }
Parameter { name: "type"; type: "int" }
Parameter { name: "id"; type: "QString" }
}
Method {
name: "removeRow"
Parameter { name: "rowIndex"; type: "int" }
Parameter { name: "rows"; type: "int" }
}
Method {
name: "removeRow"
isCloned: true
Parameter { name: "rowIndex"; type: "int" }
}
}
}
@@ -0,0 +1,14 @@
module QtQuick3D.MaterialEditor
typeinfo plugins.qmltypes
import QtQuick3D
prefer :/qt-project.org/imports/QtQuick3D/MaterialEditor/
ShaderEditor 1.0 ShaderEditor.qml
EditorView 1.0 EditorView.qml
Preview 1.0 Preview.qml
PreviewControls 1.0 PreviewControls.qml
FrostedGlass 1.0 FrostedGlass.qml
AboutDialog 1.0 AboutDialog.qml
MaterialPropertiesPane 1.0 MaterialPropertiesPane.qml
SaveChangesDialog 1.0 SaveChangesDialog.qml
UniformManagerPane 1.0 UniformManagerPane.qml