Qt Quick 3D - Principled Material Example

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick3D
import QtQuick.Dialogs

import Example

RowLayout {
    id: root
    property Texture targetTexture: null
    property bool stampMode: false
    required property url defaultTexture
    property url stampSource: ""
    property alias defaultClearColor: drawer.clearColor
    property bool envMapMode: false

    GroupBox {
        title: "Image Source Mode"
        ColumnLayout {
            RadioButton {
                id: noTextureChoice
                text: "None"
                checked: true
                onCheckedChanged: {
                    targetTexture = null
                }
            }
            RadioButton {
                id: selectTextureChoice
                text: "Texture"
                checked: false
                onCheckedChanged: targetTexture = selectedTexture
            }
            RadioButton {
                id: loadImageChoice
                text: "Load Image"
                onCheckedChanged: targetTexture = loadTextureTexture
            }
            RadioButton {
                id: drawerChoice
                text: "Draw Texture"
                onCheckedChanged: targetTexture = drawerTexture
            }
        }
    }
    Item {
        visible: selectTextureChoice.checked
        implicitWidth: 256
        implicitHeight: 256
        Image {
            id: previewImage
            anchors.fill: parent
            sourceSize.width: width
            sourceSize.height: height
            fillMode: Image.PreserveAspectFit
            source: root.defaultTexture
            Texture {
                id: selectedTexture
                source: root.defaultTexture
                mappingMode: root.envMapMode ? Texture.Environment : Texture.UV
            }
        }
    }

    Rectangle {
        id: loadTextureFrame
        implicitWidth: 256
        implicitHeight: 256
        color: "transparent"
        border.color: "black"
        visible: loadImageChoice.checked
        property url textureSource: ""
        Text {
            anchors.centerIn: parent
            text: "[Load Image]"
        }
        Image {
            anchors.fill: parent
            sourceSize.width: width
            sourceSize.height: height
            fillMode: Image.PreserveAspectFit
            visible: loadTextureFrame.textureSource !== null
            source: loadTextureFrame.textureSource
        }

        MouseArea {
            anchors.fill: parent
            onClicked: {
                textureSourceDialog.open()
            }
        }

        Texture {
            id: loadTextureTexture
            source: loadTextureFrame.textureSource
            mappingMode: root.envMapMode ? Texture.Environment : Texture.UV
        }

        ImageHelper {
            id: imageHelper
        }

        FileDialog {
            id: textureSourceDialog
            title: "Open an Image File"
            nameFilters: [ imageHelper.getSupportedImageFormatsFilter()]
            onAccepted: {
                if (textureSourceDialog.selectedFile !== null) {
                    loadTextureFrame.textureSource = textureSourceDialog.selectedFile
                }
            }
        }

    }
    ColumnLayout {
        visible: drawerChoice.checked
        Rectangle {
            implicitWidth: 260
            implicitHeight: 260
            color: "transparent"
            border.color: "black"
            Canvas {
                id: drawer
                width: 256
                height: 256
                x: 2
                y: 2

                property color penColor: "blue"
                property real penWidth: penWidthSlider.value
                property bool needsClear: true
                property color clearColor: "white"
                property var commands: []
                property var stampCommands: []
                property bool stampMode: root.stampMode
                //property point prevPoint : Qt.point(0, 0)

                onPaint: {
                    let ctx = getContext('2d');
                    if (needsClear) {
                        ctx.fillStyle = Qt.rgba(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
                        ctx.fillRect(0, 0, width, height)
                        needsClear = false;
                    }
                    if (!stampMode) {
                        ctx.strokeStyle = Qt.rgba(penColor.r, penColor.g, penColor.b, penColor.a)
                        ctx.lineCap = "round"
                        ctx.lineWidth = penWidth;

                        for (let i = 0; i < commands.length; ++i) {
                            let command = commands[i];
                            ctx.beginPath()
                            ctx.moveTo(command.start.x, command.start.y);
                            ctx.lineTo(command.end.x, command.end.y);
                            ctx.stroke();
                        }
                        commands = [];
                    } else {
                        for (let i = 0; i < stampCommands.length; ++i) {
                            let stampCommand = stampCommands[i]
                            // get offset
                            let dX = stampCommand.x - stampcursor.width * 0.5
                            let dY = stampCommand.y - stampcursor.height * 0.5
                            ctx.drawImage(stampcursor, dX, dY)
                        }
                        stampCommands = [];
                    }
                }
            }

            MouseArea {
                id: mouseArea
                anchors.fill: drawer
                enabled: drawerChoice.checked
                hoverEnabled: true
                //acceptedButtons: Qt.LeftButton
                property bool isDrawing: false
                property var lastPosition: Qt.point(0, 0)
                preventStealing: true
                clip: true

                Item {
                    id: cursor
                    Rectangle {
                        anchors.centerIn: parent
                        visible: !root.stampMode
                        width: drawer.penWidth
                        height: drawer.penWidth
                        radius: width * 0.5
                        color: drawer.penColor
                    }

                    Image {
                       id: stampcursor
                       anchors.centerIn: parent
                       visible: root.stampMode
                       source: root.stampSource
                    }
                }

                onEntered: cursor.visible = true
                onExited: cursor.visible = false

                onPressed: (mouse)=> {
                               if (mouse.button === Qt.LeftButton && !root.stampMode) {
                                   lastPosition = Qt.point(mouse.x, mouse.y)
                                   isDrawing = true
                               }
                           }
                onPositionChanged: (mouse)=> {
                                       if (isDrawing) {
                                           let pos = Qt.point(mouse.x, mouse.y);
                                           let command = {"start": lastPosition, "end": pos}
                                           drawer.commands.push(command)
                                           lastPosition = pos;
                                           drawer.requestPaint();
                                       }
                                       cursor.x = mouse.x
                                       cursor.y = mouse.y
                                   }
                onReleased: (mouse)=> {
                                if (mouse.button === Qt.LeftButton && isDrawing) {
                                    let pos = Qt.point(mouse.x, mouse.y);
                                    let command = {"start": lastPosition, "end": pos}
                                    drawer.commands.push(command)
                                    isDrawing = false;
                                    drawer.requestPaint();
                                } else if (root.stampMode) {
                                    drawer.stampCommands.push(Qt.point(mouse.x, mouse.y));
                                    drawer.requestPaint();
                                }
                            }
            }

        }
        RowLayout {
            visible: !root.stampMode
            spacing: 0
            Rectangle {
                id: whiteBrush
                implicitWidth: 25
                implicitHeight: 25
                color: "white"
                border.color: "black"
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        drawer.penColor = parent.color;
                    }
                }
            }
            Rectangle {
                id: blackBrush
                implicitWidth: 25
                implicitHeight: 25
                color: "black"
                border.color: "black"
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        drawer.penColor = parent.color;
                    }
                }
            }
            Rectangle {
                id: redBrush
                implicitWidth: 25
                implicitHeight: 25
                color: "red"
                border.color: "black"
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        drawer.penColor = parent.color;
                    }
                }
            }
            Rectangle {
                id: greenBrush
                implicitWidth: 25
                implicitHeight: 25
                color: "green"
                border.color: "black"
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        drawer.penColor = parent.color;
                    }
                }
            }
            Rectangle {
                id: blueBrush
                implicitWidth: 25
                implicitHeight: 25
                color: "blue"
                border.color: "black"
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        drawer.penColor = parent.color;
                    }
                }
            }
            Label {
                text: " "

            }

            Button {
                text: "Clear"
                onClicked: {

                    drawer.needsClear = true
                    drawer.requestPaint()
                }
            }

        }
        RowLayout {
            visible: !root.stampMode
            Slider {
                id: penWidthSlider
                from: 1
                to: 50
                value: 5
            }
            Label {
                Layout.fillWidth: true
                text: "Pen Width"
            }
        }

    }

    Texture {
        id: drawerTexture
        sourceItem: drawerChoice.checked ? drawer : null
        mappingMode: root.envMapMode ? Texture.Environment : Texture.UV
    }

}