Qt Quick 3D - Principled 材质范例
// Copyright (C) 2022 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
width: 256
height: 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: envMapMode ? Texture.Environment : Texture.UV
}
}
}
Rectangle {
id: loadTextureFrame
width: 256
height: 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: 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 {
width: 260
height: 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 (stampMode) {
drawer.stampCommands.push(Qt.point(mouse.x, mouse.y));
drawer.requestPaint();
}
}
}
}
RowLayout {
visible: !root.stampMode
spacing: 0
Rectangle {
id: whiteBrush
width: 25
height: 25
color: "white"
border.color: "black"
MouseArea {
anchors.fill: parent
onClicked: {
drawer.penColor = parent.color;
}
}
}
Rectangle {
id: blackBrush
width: 25
height: 25
color: "black"
border.color: "black"
MouseArea {
anchors.fill: parent
onClicked: {
drawer.penColor = parent.color;
}
}
}
Rectangle {
id: redBrush
width: 25
height: 25
color: "red"
border.color: "black"
MouseArea {
anchors.fill: parent
onClicked: {
drawer.penColor = parent.color;
}
}
}
Rectangle {
id: greenBrush
width: 25
height: 25
color: "green"
border.color: "black"
MouseArea {
anchors.fill: parent
onClicked: {
drawer.penColor = parent.color;
}
}
}
Rectangle {
id: blueBrush
width: 25
height: 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: envMapMode ? Texture.Environment : Texture.UV
}
}