From 15f5b3c1f0eb8ffef9d700001e979945aa09fc30 Mon Sep 17 00:00:00 2001 From: Christopher Ham Date: Mon, 16 Jan 2012 19:25:14 +1000 Subject: Adding a color picker and material editting to ModelTweaker The color picker has been added - needs clean up. Picker now updates the model properly. Texture selection is now working. And saving works. Change-Id: I031799eb2fcb5f61d1cf71d6b2d6d6d03907491a Reviewed-by: Christopher Ham Reviewed-by: Danny Pope --- util/qt3d/modeltweak/modeltweak.pro | 7 - util/qt3d/modeltweak/qml/ButtonBarPane.qml | 6 +- util/qt3d/modeltweak/qml/CheckBox.qml | 45 ++ util/qt3d/modeltweak/qml/ColorPicker.qml | 286 ++++++++ util/qt3d/modeltweak/qml/ColorUtils.js | 61 ++ util/qt3d/modeltweak/qml/ModelPropertiesPane.qml | 752 +++++++++++++-------- util/qt3d/modeltweak/qml/ModelTweak.qml | 40 ++ util/qt3d/modeltweak/qml/ModelViewport.qml | 1 + util/qt3d/modeltweak/qml/Widgets/BlenderToggle.qml | 1 + util/qt3d/modeltweak/qml/Widgets/Checkered.qml | 27 + util/qt3d/modeltweak/qml/Widgets/ColorSelector.qml | 72 ++ util/qt3d/modeltweak/qml/Widgets/ColorWidget.qml | 37 + util/qt3d/modeltweak/qml/Widgets/HSVColor.qml | 15 + util/qt3d/modeltweak/qml/Widgets/InputBox.qml | 39 ++ util/qt3d/modeltweak/qml/Widgets/SliderHandle.qml | 76 +++ util/qt3d/modeltweak/qml/fileHandling.js | 26 + 16 files changed, 1211 insertions(+), 280 deletions(-) create mode 100644 util/qt3d/modeltweak/qml/CheckBox.qml create mode 100644 util/qt3d/modeltweak/qml/ColorPicker.qml create mode 100644 util/qt3d/modeltweak/qml/ColorUtils.js create mode 100644 util/qt3d/modeltweak/qml/Widgets/Checkered.qml create mode 100644 util/qt3d/modeltweak/qml/Widgets/ColorSelector.qml create mode 100644 util/qt3d/modeltweak/qml/Widgets/ColorWidget.qml create mode 100644 util/qt3d/modeltweak/qml/Widgets/HSVColor.qml create mode 100644 util/qt3d/modeltweak/qml/Widgets/InputBox.qml create mode 100644 util/qt3d/modeltweak/qml/Widgets/SliderHandle.qml diff --git a/util/qt3d/modeltweak/modeltweak.pro b/util/qt3d/modeltweak/modeltweak.pro index d77f4f1e..285927aa 100644 --- a/util/qt3d/modeltweak/modeltweak.pro +++ b/util/qt3d/modeltweak/modeltweak.pro @@ -23,10 +23,3 @@ RC_FILE = modeltweak.rc HEADERS += \ quickfile.h - - - - - - - diff --git a/util/qt3d/modeltweak/qml/ButtonBarPane.qml b/util/qt3d/modeltweak/qml/ButtonBarPane.qml index c366fb0d..c21f16bb 100644 --- a/util/qt3d/modeltweak/qml/ButtonBarPane.qml +++ b/util/qt3d/modeltweak/qml/ButtonBarPane.qml @@ -32,8 +32,10 @@ Flow { { FileHandler.save_qml(true); } - - quickFile.load() + var useEffect = useCustomEffect; + useCustomEffect = false; + quickFile.load(); + useCustomEffect = useEffect; } buttonText: "Load Asset" imageSrc: "images/model.png" diff --git a/util/qt3d/modeltweak/qml/CheckBox.qml b/util/qt3d/modeltweak/qml/CheckBox.qml new file mode 100644 index 00000000..efadd539 --- /dev/null +++ b/util/qt3d/modeltweak/qml/CheckBox.qml @@ -0,0 +1,45 @@ +import QtQuick 1.0 + +Item { + id: checkBox + height: Math.max (textBox.height, check.height) + width: textBox.width + check.width + property alias text: textBox.text + property bool checked: false + signal clicked(bool checked) + property variant color: Qt.rgba(1,1,1,1) + + Text { + id: textBox + anchors.left: parent.left + color: checkBox.color + } + + Rectangle { + id: check + width: 16 + height: 16 + anchors.right: parent.right + + border.color: checkBox.color + border.width: 1 + color: Qt.rgba(0,0,0,0) + + Rectangle { + anchors.centerIn: parent + + border.color: checkBox.color + border.width: 1 + + width: parent.width - 6 + height: parent.height - 6 + color: checkBox.color + opacity: checked + } + } + + MouseArea { + anchors.fill: parent + onClicked: { checked = !checked; checkBox.clicked(checked) } + } +} diff --git a/util/qt3d/modeltweak/qml/ColorPicker.qml b/util/qt3d/modeltweak/qml/ColorPicker.qml new file mode 100644 index 00000000..641a6acc --- /dev/null +++ b/util/qt3d/modeltweak/qml/ColorPicker.qml @@ -0,0 +1,286 @@ +import QtQuick 1.0 +import "Widgets" +import "ColorUtils.js" as ColorUtils + +Rectangle { + id: picker + property alias alpha: alphaSlider.value + property alias hue: hueSlider.value + property alias sat: colorSelector.sat + property alias val: colorSelector.val + property variant colorSelect: Qt.hsla(hsl_hue, hsl_sat, hsl_lum, alpha) + property HSVColor targetColor + + HSVColor { id: oldColor } + + property real hsl_hue: hue + property real hsl_sat: sat*val*((hsl_lum <= 2) ? hsl_lum*2 : 2*(1 - hsl_lum)) + property real hsl_lum: ((2.0 - sat)*val)/2 + + property int red: rgb[0] + property int green: rgb[1] + property int blue: rgb[2] + + property variant rgb: ColorUtils.hsvToRgb(hue, sat, val) + + onRgbChanged: updateTarget() + onAlphaChanged: updateTarget() + + anchors.fill: parent + property real bgAlpha: 0.5 + color: Qt.rgba(1,1,1,bgAlpha) + + function apply() { + updateTarget(); + picker.visible = false; + } + + function cancel() { + resetTarget(); + picker.visible = false; + } + + function setTarget(target) { + targetColor = target; + hue = target.hue; + sat = target.sat; + val = target.val; + alpha = target.alpha; + oldColor.hue = target.hue; + oldColor.sat = target.sat; + oldColor.val = target.val; + oldColor.alpha = target.alpha + } + + function updateTarget() { + if (targetColor === null || !picker.visible) return; + targetColor.hue = hue; + targetColor.sat = sat; + targetColor.val = val; + targetColor.alpha = alpha; + } + + function resetTarget() { + if (targetColor === null) return; + targetColor.hue = oldColor.hue; + targetColor.sat = oldColor.sat; + targetColor.val = oldColor.val; + targetColor.alpha = oldColor.alpha; + } + + MouseArea { + anchors.fill: parent + onClicked: {} + } + + Rectangle { + width: pickerLayout.implicitWidth+20 + height: 250 + anchors.centerIn: parent + + border.width: 2 + border.color: "white" + color: mainwindow.color + + Row { + id: pickerLayout + anchors { + top: parent.top + horizontalCenter: parent.horizontalCenter + margins: 10 + } + height: 200 + spacing: 10 + + Item { + id: colorViewer + width: height + height: parent.height + Rectangle { + anchors.fill: parent + rotation: -90 + gradient: Gradient { + GradientStop { position: 0.0; color: Qt.hsla(hueSlider.value, 1.0, 1.0) } + GradientStop { position: 1.0; color: Qt.hsla(hueSlider.value, 1.0, 0.5) } + } + } + Rectangle { + anchors.fill: parent + border.color: "lightgray" + gradient: Gradient { + GradientStop { position: 0.0; color: Qt.rgba(0,0,0,0) } + GradientStop { position: 1.0; color: Qt.rgba(0,0,0,1) } + } + } + + ColorSelector { + id: colorSelector + anchors.fill: parent + } + } + + Item { + width: 20 + height: parent.height + Rectangle { + anchors.fill: parent + border.color: "lightgray" + gradient: Gradient { + GradientStop { position: 1.0; color: Qt.hsla(1.0, 1.0, 0.5) } + GradientStop { position: 0.85; color: Qt.hsla(0.85, 1.0, 0.5) } + GradientStop { position: 0.76; color: Qt.hsla(0.76, 1.0, 0.5) } + GradientStop { position: 0.5; color: Qt.hsla(0.5, 1.0, 0.5) } + GradientStop { position: 0.33; color: Qt.hsla(0.33, 1.0, 0.5) } + GradientStop { position: 0.16; color: Qt.hsla(0.16, 1.0, 0.5) } + GradientStop { position: 0.0; color: Qt.hsla(0.0, 1.0, 0.5) } + } + } + + SliderHandle { + id: hueSlider + anchors.fill: parent + } + } + + Item { + width: 20 + height: parent.height + + Checkered { + anchors.fill: parent + } + + Rectangle { + anchors.fill: parent + border.color: "lightgray" + gradient: Gradient { + GradientStop { + position: 0.0; + color: Qt.hsla(picker.hsl_hue, picker.hsl_sat, picker.hsl_lum) + } + GradientStop { + position: 1.0; + color: Qt.hsla(picker.hsl_hue, picker.hsl_sat, picker.hsl_lum, 0) + } + } + } + + SliderHandle { + id: alphaSlider + anchors.fill: parent + inverted: true + } + } + + Item { + width: 60 + height: parent.height + + Item { + id: preview + width: parent.width; height: 40 + Checkered { + anchors.fill: parent + } + Rectangle { + anchors.fill: parent + border.color: "lightgray" + color: picker.colorSelect + } + } + + Column { + anchors.top: preview.bottom + anchors.topMargin: 5 + spacing: 3 + width: parent.width + + InputBox { + id: redIn + width: parent.width; height: 20 + label: "R:" + } + + InputBox { + id: greenIn + width: parent.width; height: 20 + label: "G:" + } + + InputBox { + id: blueIn + width: parent.width; height: 20 + label: "B:" + } + + Binding { + target: redIn.input; property: "text" + value: Math.round(rgb[0]) + } + Binding { + target: greenIn.input; property: "text" + value: Math.round(rgb[1]) + } + Binding { + target: blueIn.input; property: "text" + value: Math.round(rgb[2]) + } + + Item { + width: 5; height: 5 + } + + InputBox { + id: hueIn + width: parent.width; height: 20 + label: "H:" + } + + InputBox { + id: satIn + width: parent.width; height: 20 + label: "S:" + } + + InputBox { + id: valIn + width: parent.width; height: 20 + label: "V:" + } + + Binding { + target: hueIn.input; property: "text" + value: Math.round(picker.hue*100)/100 + } + Binding { + target: satIn.input; property: "text" + value: Math.round(picker.sat*100)/100 + } + Binding { + target: valIn.input; property: "text" + value: Math.round(picker.val*100)/100 + } + } + } + } + + Row { + anchors { + margins: 5 + top: pickerLayout.bottom + horizontalCenter: parent.horizontalCenter + } + spacing: 10 + BlenderToggle { + width: 80 + buttonText: "Apply" + onClicked: picker.apply() + } + BlenderToggle { + width: 80 + buttonText: "Cancel" + onClicked: picker.cancel() + } + } + } +} diff --git a/util/qt3d/modeltweak/qml/ColorUtils.js b/util/qt3d/modeltweak/qml/ColorUtils.js new file mode 100644 index 00000000..5addf394 --- /dev/null +++ b/util/qt3d/modeltweak/qml/ColorUtils.js @@ -0,0 +1,61 @@ +.pragma library + +function rgbToHsv(r, g, b) { + r = r/255; g = g/255; b = b/255; + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, v = max; + + var d = max - min; + s = max == 0 ? 0 : d / max; + + if (max == min){ + h = 0; // achromatic + } else { + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + + return new Array(h, s, v); +} + +function hsvToRgb(h, s, v) { + var r, g, b; + + var i = Math.floor(h * 6); + var f = h * 6 - i; + var p = v * (1 - s); + var q = v * (1 - f * s); + var t = v * (1 - (1 - f) * s); + + switch (i % 6){ + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + case 5: r = v; g = p; b = q; break; + } + + return new Array(r * 255, g * 255, b * 255); +} + +function hsvToHsl(h, s, v) { + var ss, l; + + l = ((2.0 - s)*v); + + ss = s*v*((l <= 1) ? l : 2 - l); + + l /= 2; + + return new Array(h, ss, l); +} + +function hsvToColor(h, s, v, a) { + var hsl = hsvToHsl(h,s,v); + return Qt.hsla(hsl[0], hsl[1], hsl[2], a); +} diff --git a/util/qt3d/modeltweak/qml/ModelPropertiesPane.qml b/util/qt3d/modeltweak/qml/ModelPropertiesPane.qml index 7325402b..b83d92d4 100644 --- a/util/qt3d/modeltweak/qml/ModelPropertiesPane.qml +++ b/util/qt3d/modeltweak/qml/ModelPropertiesPane.qml @@ -1,310 +1,520 @@ import QtQuick 1.0 import Qt3D 1.0 import ModelTweak 1.0 +import "Widgets" +import "ColorUtils.js" as ColorUtils -Column { +Row { id: properties - width: posX.width - height: parent.height - spacing: 4 - + spacing: 30 property alias rotateLocked: imageR.isLocked property alias scaleLocked: imageS.isLocked property alias translateLocked: imageP.isLocked; signal changed; + Column { + width: posX.width + height: parent.height + spacing: 4 - // POSITION - Item { - id: positionPanel - width: parent.width - height: imageP.height - - Text { - anchors.left: parent.left - anchors.leftMargin: 8 - text: "Position"; - color: "#FFFFFF" - } - Image { - id: imageP - anchors.right: parent.right - anchors.rightMargin: 8 - - //Animation to pulse the lock icon if attempting to modify while locked. - property bool bounce: false - - SequentialAnimation on scale{ - running: imageP.bounce - NumberAnimation { to : 2.0; duration: 150; easing.type: "OutQuad" } - NumberAnimation { to : 1.0; duration: 150; easing.type: "OutQuad" } - NumberAnimation { to : 2.0; duration: 150; easing.type: "OutQuad" } - NumberAnimation { to : 1.0; duration: 150; easing.type: "OutQuad" } - onCompleted: imageP.bounce = false + // POSITION + Item { + id: positionPanel + width: parent.width + height: imageP.height + + Text { + anchors.left: parent.left + anchors.leftMargin: 8 + text: "Position"; + color: "#FFFFFF" } + Image { + id: imageP + anchors.right: parent.right + anchors.rightMargin: 8 - //Manage locked/unlocked state - state: "UNLOCKED" - property bool isLocked: false - - states: [ - State { - name: "UNLOCKED" - PropertyChanges { target: imageP; source: "images/unlock.png"} - PropertyChanges { target: imageP; isLocked: false;} - }, - State { - name: "LOCKED" - PropertyChanges { target: imageP; source: "images/lock.png"} - PropertyChanges { target: imageP; isLocked: true;} + //Animation to pulse the lock icon if attempting to modify while locked. + property bool bounce: false + + SequentialAnimation on scale{ + running: imageP.bounce + NumberAnimation { to : 2.0; duration: 150; easing.type: "OutQuad" } + NumberAnimation { to : 1.0; duration: 150; easing.type: "OutQuad" } + NumberAnimation { to : 2.0; duration: 150; easing.type: "OutQuad" } + NumberAnimation { to : 1.0; duration: 150; easing.type: "OutQuad" } + onCompleted: imageP.bounce = false } - ] - - MouseArea { - anchors.fill: parent - onDoubleClicked: { - if (parent.state=="LOCKED") - parent.state="UNLOCKED" - else - parent.state="LOCKED" + + //Manage locked/unlocked state + state: "UNLOCKED" + property bool isLocked: false + + states: [ + State { + name: "UNLOCKED" + PropertyChanges { target: imageP; source: "images/unlock.png"} + PropertyChanges { target: imageP; isLocked: false;} + }, + State { + name: "LOCKED" + PropertyChanges { target: imageP; source: "images/lock.png"} + PropertyChanges { target: imageP; isLocked: true;} + } + ] + + MouseArea { + anchors.fill: parent + onDoubleClicked: { + if (parent.state=="LOCKED") + parent.state="UNLOCKED" + else + parent.state="LOCKED" + } } } } - } - BlenderValueSlider { - focus: true - id: posX - label: "X:" - locked: imageP.isLocked - value: transformTranslate.translate.x.toFixed(3) - function update (f) { - transformTranslate.translate = Qt.vector3d(f, transformTranslate.translate.y, transformTranslate.translate.z); + BlenderValueSlider { + focus: true + id: posX + label: "X:" + locked: imageP.isLocked + value: transformTranslate.translate.x.toFixed(3) + function update (f) { + transformTranslate.translate = Qt.vector3d(f, transformTranslate.translate.y, transformTranslate.translate.z); + } + onNext: { updateMe(); focus = false; posY.focus = true; } + onPrev: { updateMe(); focus = false; scaleZ.focus = true; } + onFail: { imageP.bounce=true; } + onChanged: { properties.changed(); } } - onNext: { updateMe(); focus = false; posY.focus = true; } - onPrev: { updateMe(); focus = false; scaleZ.focus = true; } - onFail: { imageP.bounce=true; } - onChanged: { properties.changed(); } - } - BlenderValueSlider { - id: posY - label: "Y:" - locked: imageP.isLocked - value: transformTranslate.translate.y.toFixed(3) - function update (f) { - transformTranslate.translate = Qt.vector3d(transformTranslate.translate.x, f, transformTranslate.translate.z); + BlenderValueSlider { + id: posY + label: "Y:" + locked: imageP.isLocked + value: transformTranslate.translate.y.toFixed(3) + function update (f) { + transformTranslate.translate = Qt.vector3d(transformTranslate.translate.x, f, transformTranslate.translate.z); + } + onNext: { updateMe(); focus = false; posZ.focus = true; } + onPrev: { updateMe(); focus = false; posX.focus = true; } + onFail: { imageP.bounce=true; } + onChanged: { properties.changed(); } } - onNext: { updateMe(); focus = false; posZ.focus = true; } - onPrev: { updateMe(); focus = false; posX.focus = true; } - onFail: { imageP.bounce=true; } - onChanged: { properties.changed(); } - } - BlenderValueSlider { - id: posZ - label: "Z:" - locked: imageP.isLocked - value: transformTranslate.translate.z.toFixed(3) - function update (f) { - transformTranslate.translate = Qt.vector3d(transformTranslate.translate.x, transformTranslate.translate.y, f); + BlenderValueSlider { + id: posZ + label: "Z:" + locked: imageP.isLocked + value: transformTranslate.translate.z.toFixed(3) + function update (f) { + transformTranslate.translate = Qt.vector3d(transformTranslate.translate.x, transformTranslate.translate.y, f); + } + onNext: { updateMe(); focus = false; rotX.focus = true; } + onPrev: { updateMe(); focus = false; posY.focus = true; } + onFail: { imageP.bounce=true; } + onChanged: { properties.changed(); } } - onNext: { updateMe(); focus = false; rotX.focus = true; } - onPrev: { updateMe(); focus = false; posY.focus = true; } - onFail: { imageP.bounce=true; } - onChanged: { properties.changed(); } - } - // ROTATE - Item { - id: rotationPanel - width: parent.width - height: imageR.height - property bool dirty: false - - Text { - anchors.left: parent.left - anchors.leftMargin: 8 - text: "Rotation"; - color: "#FFFFFF" + // ROTATE + Item { + id: rotationPanel + width: parent.width + height: imageR.height + property bool dirty: false + Text { + anchors.left: parent.left + anchors.leftMargin: 8 + text: "Scale"; + color: "#FFFFFF" + } + + Image { + id: imageR + anchors.right: parent.right + anchors.rightMargin: 8 + + //Animation to pulse the lock icon if attempting to modify while locked. + property bool bounce: false + + SequentialAnimation on scale{ + running: imageR.bounce + NumberAnimation { to : 2.0; duration: 150; easing.type: "OutQuad" } + NumberAnimation { to : 1.0; duration: 150; easing.type: "OutQuad" } + NumberAnimation { to : 2.0; duration: 150; easing.type: "OutQuad" } + NumberAnimation { to : 1.0; duration: 150; easing.type: "OutQuad" } + onCompleted: imageR.bounce = false + } + + //Manage locked/unlocked state + state: "UNLOCKED" + property bool isLocked: false + + states: [ + State { + name: "UNLOCKED" + PropertyChanges { target: imageR; source: "images/unlock.png"} + PropertyChanges { target: imageR; isLocked: false;} + }, + State { + name: "LOCKED" + PropertyChanges { target: imageR; source: "images/lock.png"} + PropertyChanges { target: imageR; isLocked: true;} + } + ] + + MouseArea { + anchors.fill: parent + onDoubleClicked: { + if (parent.state=="LOCKED") + parent.state="UNLOCKED" + else + parent.state="LOCKED" + } + } + } + } + BlenderValueSlider { + id: rotX + label: "X:" + delta: 1 + locked: imageR.isLocked + min: 0; limitMin: true + max: 360; limitMax: true + value: transformRotateX.angle.toFixed(3) + function update (f) { transformRotateX.angle = f } + onNext: { updateMe(); focus = false; rotY.focus = true; } + onPrev: { updateMe(); focus = false; posZ.focus = true; } + onFail: { imageR.bounce=true; } + onChanged: { properties.changed(); } + } + BlenderValueSlider { + id: rotY + label: "Y:" + delta: 1 + locked: imageR.isLocked + min: 0; limitMin: true + max: 360; limitMax: true + value: transformRotateY.angle.toFixed(3) + function update (f) { transformRotateY.angle = f } + onNext: { updateMe(); focus = false; rotZ.focus = true; } + onPrev: { updateMe(); focus = false; rotX.focus = true; } + onFail: { imageR.bounce=true; } + onChanged: { properties.changed(); } } - Image { - id: imageR - anchors.right: parent.right - anchors.rightMargin: 8 - - //Animation to pulse the lock icon if attempting to modify while locked. - property bool bounce: false - - SequentialAnimation on scale{ - running: imageR.bounce - NumberAnimation { to : 2.0; duration: 150; easing.type: "OutQuad" } - NumberAnimation { to : 1.0; duration: 150; easing.type: "OutQuad" } - NumberAnimation { to : 2.0; duration: 150; easing.type: "OutQuad" } - NumberAnimation { to : 1.0; duration: 150; easing.type: "OutQuad" } - onCompleted: imageR.bounce = false + BlenderValueSlider { + id: rotZ + label: "Z:" + delta: 1 + locked: imageR.isLocked + min: 0; limitMin: true + max: 360; limitMax: true + value: transformRotateZ.angle.toFixed(3) + function update (f) { transformRotateZ.angle = f } + onNext: { updateMe(); focus = false; scaleX.focus = true; } + onPrev: { updateMe(); focus = false; rotY.focus = true; } + onFail: { imageR.bounce=true; } + onChanged: { properties.changed(); } + } + + // SCALE + Item { + id: scalePanel + width: parent.width + height: imageS.height + property bool dirty: false + + Item { + width: parent.width + height: effectText.height + Text { + id: effectText + anchors.left: parent.left + anchors.leftMargin: 8 + text: "Effect"; + color: "#FFFFFF" + font.bold: true + } } - //Manage locked/unlocked state - state: "UNLOCKED" - property bool isLocked: false - - states: [ - State { - name: "UNLOCKED" - PropertyChanges { target: imageR; source: "images/unlock.png"} - PropertyChanges { target: imageR; isLocked: false;} - }, - State { - name: "LOCKED" - PropertyChanges { target: imageR; source: "images/lock.png"} - PropertyChanges { target: imageR; isLocked: true;} + Image { + id: imageS + anchors.right: parent.right + anchors.rightMargin: 8 + + //Animation to pulse the lock icon if attempting to modify while locked. + property bool bounce: false + + SequentialAnimation on scale{ + running: imageS.bounce + NumberAnimation { to : 2.0; duration: 150; easing.type: "OutQuad" } + NumberAnimation { to : 1.0; duration: 150; easing.type: "OutQuad" } + NumberAnimation { to : 2.0; duration: 150; easing.type: "OutQuad" } + NumberAnimation { to : 1.0; duration: 150; easing.type: "OutQuad" } + onCompleted: imageS.bounce = false } - ] - - MouseArea { - anchors.fill: parent - onDoubleClicked: { - if (parent.state=="LOCKED") - parent.state="UNLOCKED" - else - parent.state="LOCKED" + + //Manage locked/unlocked state + state: "UNLOCKED" + property bool isLocked: false + + states: [ + State { + name: "UNLOCKED" + PropertyChanges { target: imageS; source: "images/unlock.png"} + PropertyChanges { target: imageS; isLocked: false;} + }, + State { + name: "LOCKED" + PropertyChanges { target: imageS; source: "images/lock.png"} + PropertyChanges { target: imageS; isLocked: true;} + } + ] + + MouseArea { + anchors.fill: parent + onDoubleClicked: { + if (parent.state=="LOCKED") + parent.state="UNLOCKED" + else + parent.state="LOCKED" + } } } } - } - BlenderValueSlider { - id: rotX - label: "X:" - delta: 1 - locked: imageR.isLocked - min: 0; limitMin: true - max: 360; limitMax: true - value: transformRotateX.angle.toFixed(3) - function update (f) { transformRotateX.angle = f } - onNext: { updateMe(); focus = false; rotY.focus = true; } - onPrev: { updateMe(); focus = false; posZ.focus = true; } - onFail: { imageR.bounce=true; } - onChanged: { properties.changed(); } - } - BlenderValueSlider { - id: rotY - label: "Y:" - delta: 1 - locked: imageR.isLocked - min: 0; limitMin: true - max: 360; limitMax: true - value: transformRotateY.angle.toFixed(3) - function update (f) { transformRotateY.angle = f } - onNext: { updateMe(); focus = false; rotZ.focus = true; } - onPrev: { updateMe(); focus = false; rotX.focus = true; } - onFail: { imageR.bounce=true; } - onChanged: { properties.changed(); } - } - BlenderValueSlider { - id: rotZ - label: "Z:" - delta: 1 - locked: imageR.isLocked - min: 0; limitMin: true - max: 360; limitMax: true - value: transformRotateZ.angle.toFixed(3) - function update (f) { transformRotateZ.angle = f } - onNext: { updateMe(); focus = false; scaleX.focus = true; } - onPrev: { updateMe(); focus = false; rotY.focus = true; } - onFail: { imageR.bounce=true; } - onChanged: { properties.changed(); } + BlenderValueSlider { + id: scaleX + label: "X:" + locked: imageS.isLocked + min: 0; limitMin: true + value: transformScale.scale.x.toFixed(3) + function update (f) { transformScale.scale = Qt.vector3d(f, transformScale.scale.y, transformScale.scale.z); } + onNext: { updateMe(); focus = false; scaleY.focus = true; } + onPrev: { updateMe(); focus = false; rotZ.focus = true; } + onFail: { imageS.bounce=true; } + onChanged: { properties.changed(); } + } + BlenderValueSlider { + id: scaleY + label: "Y:" + locked: imageS.isLocked + min: 0; limitMin: true + value: transformScale.scale.y.toFixed(3) + function update (f) { transformScale.scale = Qt.vector3d(transformScale.scale.x, f, transformScale.scale.z); } + onNext: { updateMe(); focus = false; scaleZ.focus = true; } + onPrev: { updateMe(); focus = false; scaleX.focus = true; } + onFail: { imageS.bounce=true; } + onChanged: { properties.changed(); } + } + BlenderValueSlider { + id: scaleZ + label: "Z:" + locked: imageS.isLocked + min: 0; limitMin: true + value: transformScale.scale.z.toFixed(3) + function update (f) { transformScale.scale = Qt.vector3d(transformScale.scale.x, transformScale.scale.y, f); } + onNext: { updateMe(); focus = false; posX.focus = true; } + onPrev: { updateMe(); focus = false; scaleY.focus = true; } + onFail: { imageS.bounce=true; } + onChanged: { properties.changed(); } + } } - // SCALE - Item { - id: scalePanel - width: parent.width - height: imageS.height - property bool dirty: false - - Text { - anchors.left: parent.left - anchors.leftMargin: 8 - text: "Scale"; - color: "#FFFFFF" + // Material, Effect + Column { + width: 150 + spacing: 5 + CheckBox { + id: fromMesh + width: parent.width + text: "From Mesh" + checked: true + onClicked: { + useCustomEffect = !checked; + if (checked) { + // A horrible hack to reload the default mesh material + // TODO: Find a better way of doing this (i.e. no runtime errors) + source_mesh.source = ""; + source_mesh.source = targetMesh; + } + } } - Image { - id: imageS - anchors.right: parent.right - anchors.rightMargin: 8 - - //Animation to pulse the lock icon if attempting to modify while locked. - property bool bounce: false - - SequentialAnimation on scale{ - running: imageS.bounce - NumberAnimation { to : 2.0; duration: 150; easing.type: "OutQuad" } - NumberAnimation { to : 1.0; duration: 150; easing.type: "OutQuad" } - NumberAnimation { to : 2.0; duration: 150; easing.type: "OutQuad" } - NumberAnimation { to : 1.0; duration: 150; easing.type: "OutQuad" } - onCompleted: imageS.bounce = false + + Column { + enabled: !fromMesh.checked + opacity: enabled ? 1.0: 0.5 + width: parent.width; spacing: 5 + + move: Transition { + NumberAnimation { + properties: "y" + easing.type: Easing.InOutQuad; + duration: 150 + } + } + + Behavior on opacity { + NumberAnimation { easing.type: Easing.InOutQuad; duration: 100 } + } + + CheckBox { + width: parent.width + text: "Decal" + checked: false + onClicked: { + modelEffect.decal = checked + } + } + + Item { + width: parent.width + height: colorText.height + Text { + id: colorText + anchors.left: parent.left + anchors.leftMargin: 8 + text: "Color"; + color: "#FFFFFF" + font.bold: true + } + } + + CheckBox { + id: simpleColor + width: parent.width + text: "Simple" + checked: true + onClicked: { + useCustomMaterial = !checked; + } + } + + ColorWidget { + id: flat + visible: opacity != 0 + opacity: simpleColor.checked + width: parent.width; height: 16 + targetColor: modelEffect.hsv + text: "Flat Color" + Behavior on opacity { + NumberAnimation { easing.type: Easing.InOutQuad; duration: 100 } + } + } + + ColorWidget { + id: ambient + visible: opacity != 0 + opacity: !flat.visible + width: parent.width; height: 16 + targetColor: modelMaterial.amb_hsv + text: "Ambient Color" + Behavior on opacity { + NumberAnimation { easing.type: Easing.InOutQuad; duration: 100 } + } + } + + ColorWidget { + id: diffuse + visible: opacity != 0 + opacity: !flat.visible + width: parent.width; height: 16 + targetColor: modelMaterial.dif_hsv + text: "Diffuse Color" + Behavior on opacity { + NumberAnimation { easing.type: Easing.InOutQuad; duration: 100 } + } + } + + ColorWidget { + id: specular + visible: opacity != 0 + opacity: !flat.visible + width: parent.width; height: 16 + targetColor: modelMaterial.spec_hsv + text: "Specular Color" + Behavior on opacity { + NumberAnimation { easing.type: Easing.InOutQuad; duration: 100 } + } + } + + Item { width: parent.width; height: 5 } + + Item { + visible: opacity != 0 + opacity: !flat.visible + height: shineText.height + shinyValue.height + 5 + width: parent.width + + Behavior on opacity { + NumberAnimation { easing.type: Easing.InOutQuad; duration: 100 } + } + + Text { + id: shineText + text: "Shininess:" + color: "white" + } + InputBox { + anchors.right: parent.right + width: parent.width - shineText.width - 5 + height: shineText.height + input.text: modelMaterial.shininess + } + Rectangle { + width: parent.width - 8; height: 1 + anchors.centerIn: shinyValue + color: "gray" + } + + SliderHandle { + id: shinyValue + width: parent.width; height: 10 + anchors { top: shineText.bottom; topMargin: 5 } + horizontal: true + onValueChanged: { + modelMaterial.shininess = Math.round(value * 128) + } + } + } + + Item { width: parent.width; height: 5 } + + Item { + width: parent.width + height: textureText.height + Text { + id: textureText + anchors.left: parent.left + anchors.leftMargin: 8 + text: "Texture"; + color: "#FFFFFF" + font.bold: true + } } - //Manage locked/unlocked state - state: "UNLOCKED" - property bool isLocked: false - - states: [ - State { - name: "UNLOCKED" - PropertyChanges { target: imageS; source: "images/unlock.png"} - PropertyChanges { target: imageS; isLocked: false;} - }, - State { - name: "LOCKED" - PropertyChanges { target: imageS; source: "images/lock.png"} - PropertyChanges { target: imageS; isLocked: true;} + Item { + width: parent.width + height: texturePreview.height + Image { + id: texturePreview + width: 60; height: width + source: textureFile.filename } - ] - - MouseArea { - anchors.fill: parent - onDoubleClicked: { - if (parent.state=="LOCKED") - parent.state="UNLOCKED" - else - parent.state="LOCKED" + Rectangle { + anchors.fill: texturePreview + border.color: "white" + border.width: 1 + color: "transparent" + } + + BlenderToggle { + width: 60; height: 20 + anchors.right: parent.right + buttonText: "Open..." + onClicked: { + textureFile.load(); + } } } } } - BlenderValueSlider { - id: scaleX - label: "X:" - locked: imageS.isLocked - min: 0; limitMin: true - value: transformScale.scale.x.toFixed(3) - function update (f) { transformScale.scale = Qt.vector3d(f, transformScale.scale.y, transformScale.scale.z); } - onNext: { updateMe(); focus = false; scaleY.focus = true; } - onPrev: { updateMe(); focus = false; rotZ.focus = true; } - onFail: { imageS.bounce=true; } - onChanged: { properties.changed(); } - } - BlenderValueSlider { - id: scaleY - label: "Y:" - locked: imageS.isLocked - min: 0; limitMin: true - value: transformScale.scale.y.toFixed(3) - function update (f) { transformScale.scale = Qt.vector3d(transformScale.scale.x, f, transformScale.scale.z); } - onNext: { updateMe(); focus = false; scaleZ.focus = true; } - onPrev: { updateMe(); focus = false; scaleX.focus = true; } - onFail: { imageS.bounce=true; } - onChanged: { properties.changed(); } - } - BlenderValueSlider { - id: scaleZ - label: "Z:" - locked: imageS.isLocked - min: 0; limitMin: true - value: transformScale.scale.z.toFixed(3) - function update (f) { transformScale.scale = Qt.vector3d(transformScale.scale.x, transformScale.scale.y, f); } - onNext: { updateMe(); focus = false; posX.focus = true; } - onPrev: { updateMe(); focus = false; scaleY.focus = true; } - onFail: { imageS.bounce=true; } - onChanged: { properties.changed(); } - } } diff --git a/util/qt3d/modeltweak/qml/ModelTweak.qml b/util/qt3d/modeltweak/qml/ModelTweak.qml index 001ad4a3..57766e86 100644 --- a/util/qt3d/modeltweak/qml/ModelTweak.qml +++ b/util/qt3d/modeltweak/qml/ModelTweak.qml @@ -42,11 +42,17 @@ import QtQuick 1.0 import Qt3D 1.0 import ModelTweak 1.0 import "fileHandling.js" as FileHandler +import "Widgets" +import "ColorUtils.js" as ColorUtils Rectangle { id: outerWindow; width: 1024 height: 768 + property alias modelEffect: customEffect + property alias modelMaterial: customMaterial + property bool useCustomEffect: false + property bool useCustomMaterial: false property bool changed: false Component.onCompleted: { @@ -95,6 +101,11 @@ Rectangle { filename: "meshes/penguin.3ds" } + QuickFile { + id: textureFile + filename: "" + } + Mesh { id: source_mesh source: quickFile.filename @@ -106,6 +117,30 @@ Rectangle { Rotation3D { id: transformRotateZ; axis: Qt.vector3d(0, 0, 1); angle: 0; } Scale3D { id: transformScale; scale: Qt.vector3d(1, 1, 1); } + Effect { + id: customEffect; + property alias hsv: hsvColor + material: useCustomMaterial ? customMaterial : null; + color: hsv.color + blending: (hsv.alpha < 1.0) + || (useCustomMaterial && (hsvAmbColor.alpha*hsvDifColor.alpha*hsvSpecColor.alpha < 1.0)) + texture: textureFile.filename + } + Material { + id: customMaterial + property alias amb_hsv: hsvAmbColor + property alias dif_hsv: hsvDifColor + property alias spec_hsv: hsvSpecColor + ambientColor: hsvAmbColor.color + diffuseColor: hsvDifColor.color + specularColor: hsvSpecColor.color + textureUrl: textureFile.filename + } + HSVColor { id: hsvColor } + HSVColor { id: hsvAmbColor } + HSVColor { id: hsvDifColor } + HSVColor { id: hsvSpecColor } + ModelViewport { id: mvpZY x: 0; @@ -214,6 +249,11 @@ Rectangle { onChanged: {outerWindow.changed=true} } + ColorPicker { + id: colorPicker + visible: false + } + HelpOverlay { id: helpOverlay visible: false diff --git a/util/qt3d/modeltweak/qml/ModelViewport.qml b/util/qt3d/modeltweak/qml/ModelViewport.qml index c3b7712e..b5188d57 100644 --- a/util/qt3d/modeltweak/qml/ModelViewport.qml +++ b/util/qt3d/modeltweak/qml/ModelViewport.qml @@ -80,6 +80,7 @@ Rectangle { transformRotateZ, transformTranslate, ] + effect: useCustomEffect ? modelEffect : null; } Effect { diff --git a/util/qt3d/modeltweak/qml/Widgets/BlenderToggle.qml b/util/qt3d/modeltweak/qml/Widgets/BlenderToggle.qml index 7e397636..aa220c36 100644 --- a/util/qt3d/modeltweak/qml/Widgets/BlenderToggle.qml +++ b/util/qt3d/modeltweak/qml/Widgets/BlenderToggle.qml @@ -13,6 +13,7 @@ Rectangle { property alias buttonText: text.text property alias imageSrc: img.source + property alias textColor: text.color signal clicked diff --git a/util/qt3d/modeltweak/qml/Widgets/Checkered.qml b/util/qt3d/modeltweak/qml/Widgets/Checkered.qml new file mode 100644 index 00000000..49bf1d82 --- /dev/null +++ b/util/qt3d/modeltweak/qml/Widgets/Checkered.qml @@ -0,0 +1,27 @@ +import QtQuick 1.0 + +Grid { + property variant color1: "#FFFFFF" + property variant color2: "#BBBBBB" + property variant cellSize: 10 + width: 20 + height: parent.height + clip: true + + rows: height/cellSize + columns: width/cellSize + Repeater { + model: parent.rows * parent.columns + Rectangle { + width: cellSize; height: cellSize; + color: { + var check; + if (columns%2) check = index; + else check = Math.floor(index/columns)%2 + index; + + if (check%2) return color1; + else return color2; + } + } + } +} diff --git a/util/qt3d/modeltweak/qml/Widgets/ColorSelector.qml b/util/qt3d/modeltweak/qml/Widgets/ColorSelector.qml new file mode 100644 index 00000000..a55a743f --- /dev/null +++ b/util/qt3d/modeltweak/qml/Widgets/ColorSelector.qml @@ -0,0 +1,72 @@ +import QtQuick 1.0 + +Item { + id: select + property real sat: 1.0 + property real val: 1.0 + width: parent.width + height: parent.height + + Binding { + target: handle; property: "x" + value: select.sat * select.width + } + Binding { + target: select; property: "sat" + value: handle.x/width + } + + Binding { + target: handle; property: "y" + value: (1.0 - select.val) * select.height + } + Binding { + target: select; property: "val" + value: 1.0 - handle.y/height + } + + Item { + id: handle + width: 20 + height: width + x: 0.0; y: 0.0 + + Rectangle { + x: -width/2 + y: x + width: parent.width-2 + height: width + radius: width/2 + + border.color: "white" + color: "transparent" + } + Rectangle { + x: -width/2 + y: x + width: parent.width + height: width + radius: width/2 + + border.width: 2 + border.color: "black" + color: "transparent" + } + } + + MouseArea { + anchors.fill: parent + function mouseEvent(mouse) { + if (mouse.buttons & Qt.LeftButton) { + handle.x = Math.max(0, Math.min(height, mouse.x)) + handle.y = Math.max(0, Math.min(height, mouse.y)) + } + } + onPressed: mouseEvent(mouse); + onPositionChanged: mouseEvent(mouse); + hoverEnabled: true + onEntered: handle.opacity = 0.5; + onExited: { if (!pressed) handle.opacity = 1.0 } + onReleased: { if (!containsMouse) handle.opacity = 1.0 } + } +} diff --git a/util/qt3d/modeltweak/qml/Widgets/ColorWidget.qml b/util/qt3d/modeltweak/qml/Widgets/ColorWidget.qml new file mode 100644 index 00000000..f60ff1b0 --- /dev/null +++ b/util/qt3d/modeltweak/qml/Widgets/ColorWidget.qml @@ -0,0 +1,37 @@ +import QtQuick 1.0 + +Item { + id: colorWidget + width: parent.width; height: 16 + + property variant targetColor + property alias text: flatText.text + + Rectangle { + height: 2 + anchors { + left: parent.left; right: flatSet.right + bottom: parent.bottom + rightMargin: flatSet.width/2 + } + color: colorWidget.targetColor.color + } + Text { + id: flatText + anchors { top: parent.top; left: parent.left } + text:"Color" + color: "white" + } + BlenderToggle { + id: flatSet + width: 32 + anchors { right: parent.right } + height: parent.height + buttonText: "Set" + radius: 6 + onClicked: { + colorPicker.setTarget(colorWidget.targetColor); + colorPicker.visible = true + } + } +} diff --git a/util/qt3d/modeltweak/qml/Widgets/HSVColor.qml b/util/qt3d/modeltweak/qml/Widgets/HSVColor.qml new file mode 100644 index 00000000..9e137428 --- /dev/null +++ b/util/qt3d/modeltweak/qml/Widgets/HSVColor.qml @@ -0,0 +1,15 @@ +import QtQuick 1.0 +import "../ColorUtils.js" as ColorUtils + +Item { + property real hue: 0.0 + property real sat: 0.0 + property real val: 1.0 + property real alpha: 1.0 + property color color: ColorUtils.hsvToColor(hue, sat, val, alpha) + property int red: rgb[0] + property int green: rgb[1] + property int blue: rgb[2] + + property variant rgb: ColorUtils.hsvToRgb(hue, sat, val) +} diff --git a/util/qt3d/modeltweak/qml/Widgets/InputBox.qml b/util/qt3d/modeltweak/qml/Widgets/InputBox.qml new file mode 100644 index 00000000..d3d31837 --- /dev/null +++ b/util/qt3d/modeltweak/qml/Widgets/InputBox.qml @@ -0,0 +1,39 @@ +import QtQuick 1.0 + +Item { + id: textInput + width: 60 + height: 20 + property alias label: lbl.text + property alias input: inputBox + +// Binding { +// target: textInput; property: "value" +// value: inputBox.text +// } +// Binding { +// target: inputBox; property: "text" +// value: { var result = textInput.value*100; return Math.round(result)/100 } +// } + + Text { + id: lbl + color: "lightgray" + anchors.left: parent.left + } + + Rectangle { + border.color: "gray" + color: "transparent" + width: 40 + height: inputBox.height + anchors.right: parent.right + TextInput { + id: inputBox + anchors { fill: parent; leftMargin: 5 } + color: "white" + text: { var result = textInput.value*100; return Math.round(result)/100 } + selectByMouse: true; readOnly: true + } + } +} diff --git a/util/qt3d/modeltweak/qml/Widgets/SliderHandle.qml b/util/qt3d/modeltweak/qml/Widgets/SliderHandle.qml new file mode 100644 index 00000000..9f72cabc --- /dev/null +++ b/util/qt3d/modeltweak/qml/Widgets/SliderHandle.qml @@ -0,0 +1,76 @@ +import QtQuick 1.0 + +Item { + id: slider + width: parent.width + height: parent.height + property real value: 0.0 + property bool inverted: false + property bool horizontal: false + + signal changed; + + Binding { + target: slider; property: "value" + value: { + var res; + var pos = horizontal ? handle.x : handle.y; + var delta = horizontal ? width-handle.width : height-handle.height; + res = pos/delta; + return inverted ? 1-res : res + } + } + Binding { + target: handle; property: "y" + value: { + var res = inverted ? 1 - slider.value : slider.value + if (!horizontal) return res*(height-handle.height); + } + } + Binding { + target: handle; property: "x" + value: { + var res = inverted ? 1 - slider.value : slider.value + if (horizontal) return res*(width-handle.width); + } + } + + Rectangle { + id: handle + width: 8 + (horizontal ? 0 : parent.width) + height: 8 + (!horizontal ? 0 : parent.height) + anchors.horizontalCenter: horizontal ? undefined : parent.horizontalCenter + anchors.verticalCenter: !horizontal ? undefined : parent.verticalCenter + radius: 2 + Behavior on opacity { + NumberAnimation { easing.type: Easing.InOutQuad; duration: 80 } + } + gradient: Gradient { + GradientStop { position: 0.0; color: "#FFFFFF" } + GradientStop { position: 1.0; color: "#FFFFFF" } + } + border.width: 1 + border.color: "darkgray" + onXChanged: slider.changed() + onYChanged: slider.changed() + } + + MouseArea { + id: mouseArea + anchors.fill: parent + function mouseEvent(mouse) { + if (mouse.buttons & Qt.LeftButton) { + if (horizontal) + handle.x = Math.max(0, Math.min(width-handle.width, mouse.x-handle.width/2)) + else + handle.y = Math.max(0, Math.min(height-handle.height, mouse.y-handle.height/2)); + } + } + onPressed: mouseEvent(mouse); + onPositionChanged: mouseEvent(mouse); + hoverEnabled: true + onEntered: handle.opacity = 0.5; + onExited: { if (!pressed) handle.opacity = 1.0 } + onReleased: { if (!containsMouse) handle.opacity = 1.0 } + } +} diff --git a/util/qt3d/modeltweak/qml/fileHandling.js b/util/qt3d/modeltweak/qml/fileHandling.js index 9867a90f..3b8b7137 100644 --- a/util/qt3d/modeltweak/qml/fileHandling.js +++ b/util/qt3d/modeltweak/qml/fileHandling.js @@ -1,5 +1,30 @@ function save_qml(closePrompt) { + var effectData = ""; + if (useCustomEffect) { + effectData += + " effect: Effect {\n" + + " decal: " + customEffect.decal + "\n" + + " blending: " + customEffect.blending + "\n"; + + if (useCustomMaterial) { + effectData += + " material: Material {\n" + + " ambientColor: \"" + modelMaterial.ambientColor + "\"\n" + + " diffuseColor: \"" + modelMaterial.diffuseColor + "\"\n" + + " specularColor: \"" + modelMaterial.specularColor + "\"\n" + + " shininess: " + modelMaterial.shininess + "\n" + + " textureUrl: \"" + modelMaterial.textureUrl + "\"\n" + + " }\n" + } else { + effectData += + " color: \"" + modelEffect.color + "\"\n" + + " texture: \"" + modelEffect.texture + "\"\n" + } + + effectData += " }\n" + } + var saveData = "// --------| WARNING |--------!!\n" + "// This is a generated file. Modifying the text or layout of \n" + @@ -58,6 +83,7 @@ function save_qml(closePrompt) { " transformRotateZ,\n" + " transformTranslate,\n" + " ]\n" + + effectData + "}\n"; quickFile.filename = source_mesh.source -- cgit v1.2.3