diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml index 19746097d41..a34d7ad9bfe 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorEditor.qml @@ -413,7 +413,7 @@ SecondColumnLayout { id: transparentIndicator icon: StudioTheme.Constants.transparent pixelSize: StudioTheme.Values.myIconFontSize * 1.4 - tooltip: qsTr("Transparent TODO") + tooltip: qsTr("Transparent") onClicked: { colorPicker.alpha = 0 colorPicker.updateColor() @@ -575,22 +575,29 @@ SecondColumnLayout { onRightMouseButtonClicked: contextMenu.popup(colorPicker) onColorInvalidated: { - if (colorPicker.saturation > 0.0 && colorPicker.lightness > 0.0) { - hueSpinBox.value = colorPicker.hue + switch (colorPicker.mode) { + case ColorPicker.Mode.HSLA: + hslHueSpinBox.value = colorPicker.hue + hslSaturationSpinBox.value = colorPicker.saturationHSL + hslLightnessSpinBox.value = colorPicker.lightness + hslAlphaSpinBox.value = colorPicker.alpha + break + + case ColorPicker.Mode.RGBA: + redSpinBox.value = (colorPicker.color.r * 255) + greenSpinBox.value = (colorPicker.color.g * 255) + blueSpinBox.value = (colorPicker.color.b * 255) + rgbAlphaSpinBox.value = (colorPicker.alpha * 255) + break + + case ColorPicker.Mode.HSVA: + default: + hsvHueSpinBox.value = colorPicker.hue + hsvSaturationSpinBox.value = colorPicker.saturationHSV + hsvValueSpinBox.value = colorPicker.value + hsvAlphaSpinBox.value = colorPicker.alpha + break } - - if (colorPicker.lightness > 0.0) - saturationSpinBox.value = colorPicker.saturation - else - colorPicker.saturation = saturationSpinBox.value - - lightnessSpinBox.value = colorPicker.lightness - hslaAlphaSpinBox.value = colorPicker.alpha - - redSpinBox.value = (colorPicker.color.r * 255) - greenSpinBox.value = (colorPicker.color.g * 255) - blueSpinBox.value = (colorPicker.color.b * 255) - rgbaAlphaSpinBox.value = (colorPicker.alpha * 255) } } @@ -766,29 +773,22 @@ SecondColumnLayout { + 4 * StudioTheme.Values.colorEditorPopupSpinBoxWidth width: implicitWidth actionIndicatorVisible: false - model: ["RGBA", "HSLA"] - onActivated: { - switch (colorMode.currentText) { - case "RGBA": - rgbaRow.visible = true - hslaRow.visible = false - break - case "HSLA": - rgbaRow.visible = false - hslaRow.visible = true - break - default: - console.log("Unknown color mode selected.") - rgbaRow.visible = true - hslaRow.visible = false - } - } + textRole: "text" + valueRole: "value" + model: [ + { value: ColorPicker.Mode.HSVA, text: "HSVA" }, + { value: ColorPicker.Mode.RGBA, text: "RGBA" }, + { value: ColorPicker.Mode.HSLA, text: "HSLA" } + ] + + onActivated: colorPicker.mode = colorMode.currentValue } } RowLayout { id: rgbaRow + visible: colorPicker.mode === ColorPicker.Mode.RGBA Layout.fillWidth: true spacing: StudioTheme.Values.controlGap @@ -847,7 +847,7 @@ SecondColumnLayout { } DoubleSpinBox { - id: rgbaAlphaSpinBox + id: rgbAlphaSpinBox width: StudioTheme.Values.colorEditorPopupSpinBoxWidth stepSize: 1 @@ -856,7 +856,7 @@ SecondColumnLayout { decimals: 0 onValueModified: { - var tmp = rgbaAlphaSpinBox.value / 255.0 + var tmp = rgbAlphaSpinBox.value / 255.0 if (colorPicker.alpha !== tmp && !colorPicker.block) { colorPicker.alpha = tmp colorPicker.updateColor() @@ -868,49 +868,109 @@ SecondColumnLayout { RowLayout { id: hslaRow - visible: false + visible: colorPicker.mode === ColorPicker.Mode.HSLA Layout.fillWidth: true spacing: StudioTheme.Values.controlGap DoubleSpinBox { - id: hueSpinBox + id: hslHueSpinBox width: StudioTheme.Values.colorEditorPopupSpinBoxWidth onValueModified: { - if (colorPicker.hue !== hueSpinBox.value && !colorPicker.block) { - colorPicker.hue = hueSpinBox.value + if (colorPicker.hue !== hslHueSpinBox.value + && !colorPicker.block) { + colorPicker.hue = hslHueSpinBox.value colorPicker.updateColor() } } } DoubleSpinBox { - id: saturationSpinBox + id: hslSaturationSpinBox width: StudioTheme.Values.colorEditorPopupSpinBoxWidth onValueModified: { - if (colorPicker.saturation !== saturationSpinBox.value && !colorPicker.block) { - colorPicker.saturation = saturationSpinBox.value + if (colorPicker.saturationHSL !== hslSaturationSpinBox.value + && !colorPicker.block) { + colorPicker.saturationHSL = hslSaturationSpinBox.value colorPicker.updateColor() } } } DoubleSpinBox { - id: lightnessSpinBox + id: hslLightnessSpinBox width: StudioTheme.Values.colorEditorPopupSpinBoxWidth onValueModified: { - if (colorPicker.lightness !== lightnessSpinBox.value && !colorPicker.block) { - colorPicker.lightness = lightnessSpinBox.value + if (colorPicker.lightness !== hslLightnessSpinBox.value + && !colorPicker.block) { + colorPicker.lightness = hslLightnessSpinBox.value colorPicker.updateColor() } } } DoubleSpinBox { - id: hslaAlphaSpinBox + id: hslAlphaSpinBox width: StudioTheme.Values.colorEditorPopupSpinBoxWidth onValueModified: { - if (colorPicker.alpha !== hslaAlphaSpinBox.value && !colorPicker.block) { - colorPicker.alpha = hslaAlphaSpinBox.value + if (colorPicker.alpha !== hslAlphaSpinBox.value + && !colorPicker.block) { + colorPicker.alpha = hslAlphaSpinBox.value + colorPicker.updateColor() + } + } + } + } + + RowLayout { + id: hsvaRow + + visible: colorPicker.mode === ColorPicker.Mode.HSVA + Layout.fillWidth: true + spacing: StudioTheme.Values.controlGap + + DoubleSpinBox { + id: hsvHueSpinBox + width: StudioTheme.Values.colorEditorPopupSpinBoxWidth + onValueModified: { + if (colorPicker.hue !== hsvHueSpinBox.value + && !colorPicker.block) { + colorPicker.hue = hsvHueSpinBox.value + colorPicker.updateColor() + } + } + } + + DoubleSpinBox { + id: hsvSaturationSpinBox + width: StudioTheme.Values.colorEditorPopupSpinBoxWidth + onValueModified: { + if (colorPicker.saturationHSV !== hsvSaturationSpinBox.value + && !colorPicker.block) { + colorPicker.saturationHSV = hsvSaturationSpinBox.value + colorPicker.updateColor() + } + } + } + + DoubleSpinBox { + id: hsvValueSpinBox + width: StudioTheme.Values.colorEditorPopupSpinBoxWidth + onValueModified: { + if (colorPicker.value !== hsvValueSpinBox.value + && !colorPicker.block) { + colorPicker.value = hsvValueSpinBox.value + colorPicker.updateColor() + } + } + } + + DoubleSpinBox { + id: hsvAlphaSpinBox + width: StudioTheme.Values.colorEditorPopupSpinBoxWidth + onValueModified: { + if (colorPicker.alpha !== hsvAlphaSpinBox.value + && !colorPicker.block) { + colorPicker.alpha = hsvAlphaSpinBox.value colorPicker.updateColor() } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorPicker.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorPicker.qml index 07fc29a64d3..13f43f79955 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorPicker.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ColorPicker.qml @@ -29,15 +29,26 @@ import StudioTheme 1.0 as StudioTheme Column { id: root + enum Mode { + HSVA, + RGBA, + HSLA + } + + property int mode: ColorPicker.Mode.HSVA property color color - property real alpha: 1 property real hue: 0 - property real saturation: 0 + property real saturationHSL: 0 + property real saturationHSV: 0 property real lightness: 0 + property real value: 0 + + property real alpha: 1 + + property bool achromatic: false property int sliderMargins: 6 - property bool block: false signal updateColor @@ -46,30 +57,84 @@ Column { spacing: 10 - onAlphaChanged: invalidateColor() - onSaturationChanged: invalidateColor() - onLightnessChanged: invalidateColor() - onHueChanged: invalidateColor() - onColorChanged: { - var myAlpha = root.color.a - rgbToHsl(root.color) - root.alpha = myAlpha + onModeChanged: { + switch (root.mode) { + case ColorPicker.Mode.RGBA: + root.color = Qt.rgba(root.color.r, root.color.g, root.color.b, root.alpha) + break + case ColorPicker.Mode.HSLA: + root.color = Qt.hsla(root.hue, root.saturationHSL, root.lightness, root.alpha) + break + case ColorPicker.Mode.HSVA: + default: + root.color = Qt.hsva(root.hue, root.saturationHSV, root.value, root.alpha) + break + } + + gradientOverlay.requestPaint() } + onHueChanged: { + if (root.mode === ColorPicker.Mode.HSLA) + root.color.hslHue = root.hue + else + root.color.hsvHue = root.hue + } + onSaturationHSLChanged: { + root.color.hslSaturation = root.saturationHSL + invalidateColor() + } + onSaturationHSVChanged: { + root.color.hsvSaturation = root.saturationHSV + } + onLightnessChanged: { + root.color.hslLightness = root.lightness + } + onValueChanged: { + root.color.hsvValue = root.value + } + onAlphaChanged: invalidateColor() + onColorChanged: invalidateColor() + function invalidateColor() { if (root.block) return root.block = true - root.color = Qt.hsla(root.hue, - root.saturation, - root.lightness, - root.alpha) + if (root.color.hsvSaturation > 0.0 + && root.color.hsvValue > 0.0 + && root.color.hsvHue !== -1.0) + root.hue = root.color.hsvHue - if (root.saturation > 0.0 && root.lightness > 0.0) - hueSlider.value = root.hue + if (root.color.hslSaturation > 0.0 + && root.color.hslLightness > 0.0 + && root.color.hslHue !== -1.0) + root.hue = root.color.hslHue + if (root.color.hslLightness !== 0.0 && root.color.hslLightness !== 1.0 && !root.achromatic) + root.saturationHSL = root.color.hslSaturation + + if (root.color.hsvValue !== 0.0 && root.color.hsvValue !== 1.0 && !root.achromatic) + root.saturationHSV = root.color.hsvSaturation + + root.lightness = root.color.hslLightness + root.value = root.color.hsvValue + + if (root.color.hslLightness === 0.0 || root.color.hslLightness === 1.0 + || root.color.hsvValue === 0.0 || root.color.hsvValue === 1.0 + || root.color.hsvHue === -1.0 || root.color.hslHue === -1.0) + root.achromatic = true + else + root.achromatic = false + + if (root.mode === ColorPicker.Mode.HSLA) + root.color = Qt.hsla(root.hue, root.saturationHSL, root.lightness, root.alpha) + else + root.color = Qt.hsva(root.hue, root.saturationHSV, root.value, root.alpha) + + luminanceSlider.value = (1.0 - root.value) + hueSlider.value = root.hue opacitySlider.value = (1.0 - root.alpha) root.colorInvalidated() @@ -77,39 +142,51 @@ Column { root.block = false } - function rgbToHsl(color) { - var r = color.r - var g = color.g - var b = color.b + function drawHSVA(ctx) { + for (var row = 0; row < gradientOverlay.height; row++) { + var gradient = ctx.createLinearGradient(0, 0, gradientOverlay.width, 0) + var v = Math.abs(row - gradientOverlay.height) / gradientOverlay.height - var max = Math.max(r, g, b), min = Math.min(r, g, b) - var h, s, l = (max + min) / 2 + gradient.addColorStop(0, Qt.hsva(root.hue, 0, v, 1)) + gradient.addColorStop(1, Qt.hsva(root.hue, 1, v, 1)) - if (max === min) { - h = 0 - s = 0 - } else { - var d = max - min - s = l > 0.5 ? d / (2 - max - min) : d / (max + min) - 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 + ctx.fillStyle = gradient + ctx.fillRect(0, row, gradientOverlay.width, 1) } + } - root.block = true + function drawRGBA(ctx) { + var gradient = ctx.createLinearGradient(0, 0, gradientOverlay.width, 0) + gradient.addColorStop(0.000, Qt.rgba(1, 0, 0, 1)) + gradient.addColorStop(0.167, Qt.rgba(1, 1, 0, 1)) + gradient.addColorStop(0.333, Qt.rgba(0, 1, 0, 1)) + gradient.addColorStop(0.500, Qt.rgba(0, 1, 1, 1)) + gradient.addColorStop(0.667, Qt.rgba(0, 0, 1, 1)) + gradient.addColorStop(0.833, Qt.rgba(1, 0, 1, 1)) + gradient.addColorStop(1.000, Qt.rgba(1, 0, 0, 1)) - if (s > 0) - root.hue = h + ctx.fillStyle = gradient + ctx.fillRect(0, 0, gradientOverlay.width, gradientOverlay.height) - root.saturation = s - root.lightness = l + gradient = ctx.createLinearGradient(0, 0, 0, gradientOverlay.height) + gradient.addColorStop(0.000, Qt.rgba(0, 0, 0, 0)) + gradient.addColorStop(1.000, Qt.rgba(1, 1, 1, 1)) - root.block = false - invalidateColor() + ctx.fillStyle = gradient + ctx.fillRect(0, 0, gradientOverlay.width, gradientOverlay.height) + } + + function drawHSLA(ctx) { + for (var row = 0; row < gradientOverlay.height; row++) { + var gradient = ctx.createLinearGradient(0, 0, gradientOverlay.width, 0) + var l = Math.abs(row - gradientOverlay.height) / gradientOverlay.height + + gradient.addColorStop(0, Qt.hsla(root.hue, 0, l, 1)) + gradient.addColorStop(1, Qt.hsla(root.hue, 1, l, 1)) + + ctx.fillStyle = gradient + ctx.fillRect(0, row, gradientOverlay.width, 1) + } } Rectangle { @@ -133,26 +210,30 @@ Column { Canvas { id: gradientOverlay - property real hue: root.hue - anchors.fill: parent - opacity: root.alpha + opacity: root.color.a + + Connections { + target: root + function onHueChanged() { gradientOverlay.requestPaint() } + } - onHueChanged: requestPaint() onPaint: { var ctx = gradientOverlay.getContext('2d') ctx.save() ctx.clearRect(0, 0, gradientOverlay.width, gradientOverlay.height) - for (var row = 0; row < gradientOverlay.height; row++) { - var gradient = ctx.createLinearGradient(0, 0, gradientOverlay.width,0) - var l = Math.abs(row - gradientOverlay.height) / gradientOverlay.height - - gradient.addColorStop(0, Qt.hsla(gradientOverlay.hue, 0, l, 1)) - gradient.addColorStop(1, Qt.hsla(gradientOverlay.hue, 1, l, 1)) - - ctx.fillStyle = gradient - ctx.fillRect(0, row, gradientOverlay.width, 1) + switch (root.mode) { + case ColorPicker.Mode.RGBA: + root.drawRGBA(ctx) + break + case ColorPicker.Mode.HSLA: + root.drawHSLA(ctx) + break + case ColorPicker.Mode.HSVA: + default: + root.drawHSVA(ctx) + break } ctx.restore() @@ -162,25 +243,41 @@ Column { Canvas { id: pickerCross - property real cavnasSaturation: root.saturation - property real canvasLightness: root.lightness property color strokeStyle: "lightGray" opacity: 0.8 anchors.fill: parent antialiasing: true - onCavnasSaturationChanged: requestPaint(); - onCanvasLightnessChanged: requestPaint(); + Connections { + target: root + function onColorInvalidated() { pickerCross.requestPaint() } + function onColorChanged() { pickerCross.requestPaint() } + function onModeChanged() { pickerCross.requestPaint() } + } + onPaint: { var ctx = pickerCross.getContext('2d') - ctx.save() - ctx.clearRect(0, 0, pickerCross.width, pickerCross.height) - var yy = pickerCross.height -root.lightness * pickerCross.height - var xx = root.saturation * pickerCross.width + var yy, xx = 0 + + switch (root.mode) { + case ColorPicker.Mode.RGBA: + yy = pickerCross.height - root.saturationHSV * pickerCross.height + xx = root.hue * pickerCross.width + break + case ColorPicker.Mode.HSLA: + yy = pickerCross.height - root.lightness * pickerCross.height + xx = root.saturationHSL * pickerCross.width + break + case ColorPicker.Mode.HSVA: + default: + yy = pickerCross.height - root.value * pickerCross.height + xx = root.saturationHSV * pickerCross.width + break + } ctx.strokeStyle = pickerCross.strokeStyle ctx.lineWidth = 1 @@ -200,24 +297,37 @@ Column { } MouseArea { - id: mapMouseArea + id: mouseArea anchors.fill: parent preventStealing: true acceptedButtons: Qt.LeftButton | Qt.RightButton onPositionChanged: function(mouse) { - if (pressed && mouse.buttons === Qt.LeftButton) { + if (mouseArea.pressed && mouse.buttons === Qt.LeftButton) { var xx = Math.max(0, Math.min(mouse.x, parent.width)) var yy = Math.max(0, Math.min(mouse.y, parent.height)) - root.lightness = 1.0 - yy / parent.height - root.saturation = xx / parent.width + switch (root.mode) { + case ColorPicker.Mode.RGBA: + root.saturationHSV = 1.0 - yy / parent.height + root.hue = xx / parent.width + break + case ColorPicker.Mode.HSLA: + root.saturationHSL = xx / parent.width + root.lightness = 1.0 - yy / parent.height + break + case ColorPicker.Mode.HSVA: + default: + root.saturationHSV = xx / parent.width + root.value = 1.0 - yy / parent.height + break + } } } onPressed: function(mouse) { if (mouse.button === Qt.LeftButton) - positionChanged(mouse) + mouseArea.positionChanged(mouse) } onReleased: function(mouse) { if (mouse.button === Qt.LeftButton) @@ -233,10 +343,23 @@ Column { HueSlider { id: hueSlider + visible: root.mode !== ColorPicker.Mode.RGBA width: parent.width onValueChanged: { - if (root.hue !== value) - root.hue = value + if (root.hue !== hueSlider.value) + root.hue = hueSlider.value + } + onClicked: root.updateColor() + } + + LuminanceSlider { + id: luminanceSlider + visible: root.mode === ColorPicker.Mode.RGBA + width: parent.width + color: Qt.hsva(root.hue, root.color.hsvSaturation, 1, 1) + onValueChanged: { + if (root.value !== luminanceSlider.value) + root.value = (1.0 - luminanceSlider.value) } onClicked: root.updateColor() } @@ -246,8 +369,8 @@ Column { width: parent.width color: Qt.rgba(root.color.r, root.color.g, root.color.b, 1) onValueChanged: { - if (root.alpha !== value) - root.alpha = (1.0 - value) + if (root.alpha !== opacitySlider.value) + root.alpha = (1.0 - opacitySlider.value) } onClicked: root.updateColor() } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/LuminanceSlider.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/LuminanceSlider.qml new file mode 100644 index 00000000000..a1fe726a9cd --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/LuminanceSlider.qml @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.15 +import StudioTheme 1.0 as StudioTheme + +Item { + id: root + + property real value: 1 + property real minimum: 0 + property real maximum: 1 + property bool pressed: mouseArea.pressed + property bool integer: false + property color color + + signal clicked + + height: StudioTheme.Values.hueSliderHeight + + function updatePos() { + if (root.maximum > root.minimum) { + var pos = (track.width - handle.width) * (root.value - root.minimum) / (root.maximum - root.minimum) + return Math.min(Math.max(pos, 0), track.width - handle.width) + } else { + return 0 + } + } + + Item { + id: track + + width: parent.width + height: parent.height + + Image { + id: checkerboard + anchors.fill: parent + source: "images/checkers.png" + fillMode: Image.Tile + } + + Rectangle { + anchors.fill: parent + border.color: StudioTheme.Values.themeControlOutline + border.width: StudioTheme.Values.border + + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.000; color: root.color } + GradientStop { position: 1.000; color: "black" } + } + } + + Rectangle { + id: handle + width: StudioTheme.Values.hueSliderHandleWidth + height: track.height - 4 + anchors.verticalCenter: parent.verticalCenter + smooth: true + color: "transparent" + radius: 2 + border.color: "black" + border.width: 1 + x: root.updatePos() + y: 2 + z: 1 + + Rectangle { + anchors.fill: parent + anchors.margins: 1 + color: "transparent" + radius: 1 + border.color: "white" + border.width: 1 + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + preventStealing: true + + function calculateValue() { + var handleX = Math.max(0, Math.min(mouseArea.mouseX, mouseArea.width)) + var realValue = (root.maximum - root.minimum) * handleX / mouseArea.width + root.minimum + root.value = root.integer ? Math.round(realValue) : realValue + } + + onPressed: calculateValue() + onReleased: root.clicked() + onPositionChanged: { + if (pressed) + calculateValue() + } + } + + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir index 13e7de2f885..c0ad517e36c 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir @@ -45,6 +45,7 @@ Label 2.0 Label.qml LineEdit 2.0 LineEdit.qml LinkIndicator2D 2.0 LinkIndicator2D.qml ListViewComboBox 2.0 ListViewComboBox.qml +LuminanceSlider 2.0 LuminanceSlider.qml MarginSection 2.0 MarginSection.qml MultiIconLabel 2.0 MultiIconLabel.qml OpacitySlider 2.0 OpacitySlider.qml