Qt Quick 3D - Scene Effects Example
// 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
Item {
id: colorPicker
property bool isHovered: false
property alias color: root.color
width: 100
height: 26
Rectangle {
id: border
color: "transparent"
border.width: 2
border.color: isHovered ? palette.dark : palette.alternateBase
anchors.fill: parent
Image {
anchors.fill: parent
anchors.margins: 4
source: "qrc:/images/grid_8x8.png"
fillMode: Image.Tile
}
Rectangle {
anchors.fill: parent
anchors.margins: 4
color: colorPicker.color
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: colorPicker.isHovered = true
onExited: colorPicker.isHovered = false
onClicked: {
colorPickerPopup.open()
}
}
}
Dialog {
id: colorPickerPopup
title: "Color Picker"
anchors.centerIn: Overlay.overlay
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
RowLayout {
anchors.centerIn: parent
id: root
property color color: "red"
spacing: 0
Item {
width: 135
height: 135
ShaderEffect {
id: hsvWheel
anchors.centerIn: parent
blending: true
width: 128
height: 128
property real value: 1.0
fragmentShader: "qrc:/shaders/huesaturation.frag.qsb"
Item {
id: reticule
function reflectColor(hue, saturation) {
let angleDegrees = hue * 360
if (angleDegrees > 180)
angleDegrees = angleDegrees - 360
let angleRadians = angleDegrees * (Math.PI / 180)
let vector = Qt.vector2d(Math.cos(angleRadians), Math.sin(angleRadians)).normalized().times(0.5).times(saturation)
vector = vector.plus(Qt.vector2d(0.5, 0.5))
reticule.x = vector.x * hsvWheel.width
reticule.y = vector.y * hsvWheel.width
}
Rectangle {
x: -5
y: -5
width: 10
height: 10
radius: 5
color: "transparent"
border.width: 1
border.color: "black"
Rectangle {
x: 0.5
y: 0.5
width: 9
height: 9
radius: 4.5
color: root.color
border.width: 1
border.color: "white"
}
}
}
MouseArea {
anchors.fill: parent
function handleMouseMove(x, y, width) {
let normalizedX = x / width - 0.5;
let normalizedY = y / width - 0.5;
let angle = Math.atan2(normalizedY, normalizedX);
let toCenter = Qt.vector2d(normalizedX, normalizedY);
let radius = toCenter.length() * 2.0;
let degrees = angle * (180 / Math.PI)
if (degrees < 0)
degrees = 360 + degrees
let hue = degrees / 360
root.color = Qt.hsva(hue, radius, root.color.hsvValue, root.color.a)
if (radius <= 1.0)
return Qt.vector2d(x, y)
// Limit to radius of 1.0
toCenter = toCenter.normalized();
let halfWidth = width * 0.5;
let newX = halfWidth * toCenter.x + halfWidth
let newY = halfWidth * toCenter.y + halfWidth
return Qt.vector2d(newX, newY)
}
onPositionChanged: (mouse) => {
let pos = handleMouseMove(mouse.x, mouse.y, hsvWheel.width)
reticule.x = pos.x;
reticule.y = pos.y;
}
}
}
}
Component.onCompleted: {
updateColorSections()
reticule.reflectColor(root.color.hsvHue, root.color.hsvSaturation)
}
Connections {
target: root
function onColorChanged() {
root.updateColorSections()
}
}
function updateColorSections() {
rgbSection.redValue = root.color.r * 255
rgbSection.greenValue = root.color.g * 255
rgbSection.blueValue = root.color.b * 255
hsvSection.hueValue = root.color.hsvHue * 360
hsvSection.saturationValue = root.color.hsvSaturation * 100
hsvSection.valueValue = root.color.hsvValue * 100
alphaSection.alphaValue = root.color.a * 255
}
ColumnLayout {
width: 250
SectionLayout {
title: "RGB"
id: rgbSection
property int redValue: 0
property int greenValue: 0
property int blueValue: 0
function updateRGB() {
root.color = Qt.rgba(redValue / 255, greenValue / 255, blueValue / 255, alphaSection.alphaValue / 255)
reticule.reflectColor(root.color.hsvHue, root.color.hsvSaturation)
}
RowLayout {
Label {
text: "R:"
}
Slider {
id: redSlider
Layout.fillWidth: true
from: 0
to: 255
value: rgbSection.redValue
onValueChanged: {
if (value !== rgbSection.redValue) {
rgbSection.redValue = value
if (activeFocus)
rgbSection.updateRGB()
}
}
}
SpinBox {
from: 0
to: 255
value: rgbSection.redValue
onValueChanged: {
if (value !== rgbSection.redValue) {
rgbSection.redValue = value
if (activeFocus)
rgbSection.updateRGB()
}
}
}
}
RowLayout {
Label {
text: "G:"
}
Slider {
id: greenSlider
Layout.fillWidth: true
from: 0
to: 255
value: rgbSection.greenValue
onValueChanged: {
if (value !== rgbSection.greenValue) {
rgbSection.greenValue = value
if (activeFocus)
rgbSection.updateRGB()
}
}
}
SpinBox {
from: 0
to: 255
value: rgbSection.greenValue
onValueChanged: {
if (value !== rgbSection.greenValue) {
rgbSection.greenValue = value
if (activeFocus)
rgbSection.updateRGB()
}
}
}
}
RowLayout {
Label {
text: "B:"
}
Slider {
id: blueSlider
Layout.fillWidth: true
from: 0
to: 255
value: rgbSection.blueValue
onValueChanged: {
if (value !== rgbSection.blueValue) {
rgbSection.blueValue = value
if (activeFocus)
rgbSection.updateRGB()
}
}
}
SpinBox {
from: 0
to: 255
value: rgbSection.blueValue
onValueChanged: {
if (value !== rgbSection.blueValue) {
rgbSection.blueValue = value
if (activeFocus)
rgbSection.updateRGB()
}
}
}
}
RowLayout {
Label {
text: "Hex:"
Layout.fillWidth: true
}
TextField {
id: hexTextField
maximumLength: 6
implicitWidth: 75
function updateText() {
if (activeFocus)
return
let redText = rgbSection.redValue.toString(16).toUpperCase()
let greenText = rgbSection.greenValue.toString(16).toUpperCase()
let blueText = rgbSection.blueValue.toString(16).toUpperCase()
if (redText.length == 1)
redText = "0" + redText
if (greenText.length == 1)
greenText = "0" + greenText
if (blueText.length == 1)
blueText = "0" + blueText
text = redText + greenText + blueText;
}
function expandText(text) {
let newText = text.toUpperCase()
let expandLength = 6 - newText.length
for (let i = 0; i < expandLength; ++i)
newText = "0" + newText
return newText
}
Component.onCompleted: updateText()
validator: RegularExpressionValidator {
regularExpression: /^[0-9A-Fa-f]{0,6}$/
}
onTextChanged: {
if (!acceptableInput)
return;
let colorText = expandText(text)
rgbSection.redValue = parseInt(colorText.substr(0, 2), 16)
rgbSection.greenValue = parseInt(colorText.substr(2, 2), 16)
rgbSection.blueValue = parseInt(colorText.substr(4, 2), 16)
if (activeFocus)
rgbSection.updateRGB()
}
onAccepted: {
text = expandText(text)
}
}
Connections {
target: rgbSection
function onRedValueChanged() {
hexTextField.updateText()
}
function onGreenValueChanged() {
hexTextField.updateText()
}
function onBlueValueChanged() {
hexTextField.updateText()
}
}
}
}
SectionLayout {
title: "HSV"
id: hsvSection
property int hueValue: 0
property int saturationValue: 0
property int valueValue: 0
function updateHSV() {
root.color = Qt.hsva(hueValue / 360, saturationValue / 100, valueValue / 100, alphaSection.alphaValue / 255)
reticule.reflectColor(root.color.hsvHue, root.color.hsvSaturation)
}
RowLayout {
Label {
text: "H:"
}
Slider {
id: hueSlider
Layout.fillWidth: true
from: 0
to: 360
value: hsvSection.hueValue
onValueChanged: {
if (value !== hsvSection.hueValue) {
hsvSection.hueValue = value
if (activeFocus)
hsvSection.updateHSV()
}
}
}
SpinBox {
from: 0
to: 360
value: hsvSection.hueValue
onValueChanged: {
if (value !== hsvSection.hueValue) {
hsvSection.hueValue = value
if (activeFocus)
hsvSection.updateHSV()
}
}
}
}
RowLayout {
Label {
text: "S:"
}
Slider {
id: saturationSlider
Layout.fillWidth: true
from: 0
to: 100
value: hsvSection.saturationValue
onValueChanged: {
if (value !== hsvSection.saturationValue) {
hsvSection.saturationValue = value
if (activeFocus)
hsvSection.updateHSV()
}
}
}
SpinBox {
from: 0
to: 100
value: hsvSection.saturationValue
onValueChanged: {
if (value !== hsvSection.saturationValue) {
hsvSection.saturationValue = value
if (activeFocus)
hsvSection.updateHSV()
}
}
}
}
RowLayout {
Label {
text: "V:"
}
Slider {
id: valueSlider
Layout.fillWidth: true
from: 0
to: 100
value: hsvSection.valueValue
onValueChanged: {
if (value !== hsvSection.valueValue) {
hsvSection.valueValue = value
if (activeFocus)
hsvSection.updateHSV()
}
}
}
SpinBox {
from: 0
to: 100
value: hsvSection.valueValue
onValueChanged: {
if (value !== hsvSection.valueValue) {
hsvSection.valueValue = value
if (activeFocus)
hsvSection.updateHSV()
}
}
}
}
}
SectionLayout {
title: "Opacity / Alpha"
id: alphaSection
property int alphaValue: 0
RowLayout {
Label {
text: "V:"
}
Slider {
id: alphaSlider
Layout.fillWidth: true
from: 0
to: 255
value: alphaSection.alphaValue
onValueChanged: {
if (value !== alphaSection.alphaValue) {
alphaSection.alphaValue = value
if (activeFocus)
hsvSection.updateHSV()
}
}
}
SpinBox {
from: 0
to: 255
value: alphaSection.alphaValue
onValueChanged: {
if (value !== alphaSection.alphaValue) {
alphaSection.alphaValue = value
if (activeFocus)
hsvSection.updateHSV()
}
}
}
}
}
}
}
}
}