1/**************************************************************************** 2** 3** Copyright (C) 2016 The Qt Company Ltd. 4** Contact: https://www.qt.io/licensing/ 5** 6** This file is part of the Qt Quick Dialogs module of the Qt Toolkit. 7** 8** $QT_BEGIN_LICENSE:LGPL$ 9** Commercial License Usage 10** Licensees holding valid commercial Qt licenses may use this file in 11** accordance with the commercial license agreement provided with the 12** Software or, alternatively, in accordance with the terms contained in 13** a written agreement between you and The Qt Company. For licensing terms 14** and conditions see https://www.qt.io/terms-conditions. For further 15** information use the contact form at https://www.qt.io/contact-us. 16** 17** GNU Lesser General Public License Usage 18** Alternatively, this file may be used under the terms of the GNU Lesser 19** General Public License version 3 as published by the Free Software 20** Foundation and appearing in the file LICENSE.LGPL3 included in the 21** packaging of this file. Please review the following information to 22** ensure the GNU Lesser General Public License version 3 requirements 23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 24** 25** GNU General Public License Usage 26** Alternatively, this file may be used under the terms of the GNU 27** General Public License version 2.0 or (at your option) the GNU General 28** Public license version 3 or any later version approved by the KDE Free 29** Qt Foundation. The licenses are as published by the Free Software 30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 31** included in the packaging of this file. Please review the following 32** information to ensure the GNU General Public License requirements will 33** be met: https://www.gnu.org/licenses/gpl-2.0.html and 34** https://www.gnu.org/licenses/gpl-3.0.html. 35** 36** $QT_END_LICENSE$ 37** 38****************************************************************************/ 39 40import QtQuick 2.4 41import QtQuick.Controls 1.2 42import QtQuick.Controls.Private 1.0 43import QtQuick.Dialogs 1.0 44import QtQuick.Window 2.1 45import "qml" 46 47AbstractColorDialog { 48 id: root 49 property bool __valueSet: true // guard to prevent binding loops 50 function __setControlsFromColor() { 51 __valueSet = false 52 hueSlider.value = root.currentHue 53 saturationSlider.value = root.currentSaturation 54 lightnessSlider.value = root.currentLightness 55 alphaSlider.value = root.currentAlpha 56 crosshairs.x = root.currentLightness * paletteMap.width 57 crosshairs.y = (1.0 - root.currentSaturation) * paletteMap.height 58 __valueSet = true 59 } 60 onCurrentColorChanged: __setControlsFromColor() 61 62 Rectangle { 63 id: content 64 implicitHeight: Math.min(root.__maximumDimension, Screen.pixelDensity * (usePaletteMap ? 120 : 50)) 65 implicitWidth: Math.min(root.__maximumDimension, usePaletteMap ? Screen.pixelDensity * (usePaletteMap ? 100 : 50) : implicitHeight * 1.5) 66 color: palette.window 67 focus: root.visible 68 property real bottomMinHeight: sliders.height + buttonRow.height + outerSpacing * 3 69 property real spacing: 8 70 property real outerSpacing: 12 71 property bool usePaletteMap: true 72 73 Keys.onPressed: { 74 event.accepted = true 75 switch (event.key) { 76 case Qt.Key_Return: 77 case Qt.Key_Select: 78 accept() 79 break 80 case Qt.Key_Escape: 81 case Qt.Key_Back: 82 reject() 83 break 84 case Qt.Key_C: 85 if (event.modifiers & Qt.ControlModifier) 86 colorField.copyAll() 87 break 88 case Qt.Key_V: 89 if (event.modifiers & Qt.ControlModifier) { 90 colorField.paste() 91 root.currentColor = colorField.text 92 } 93 break 94 default: 95 // do nothing 96 event.accepted = false 97 break 98 } 99 } 100 101 SystemPalette { id: palette } 102 103 Item { 104 id: paletteFrame 105 visible: content.usePaletteMap 106 anchors { 107 top: parent.top 108 left: parent.left 109 right: parent.right 110 margins: content.outerSpacing 111 } 112 height: Math.min(content.height - content.bottomMinHeight, content.width - content.outerSpacing * 2) 113 114 Image { 115 id: paletteMap 116 x: (parent.width - width) / 2 117 width: height 118 onWidthChanged: root.__setControlsFromColor() 119 height: parent.height 120 source: "images/checkers.png" 121 fillMode: Image.Tile 122 123 // note we smoothscale the shader from a smaller version to improve performance 124 ShaderEffect { 125 id: map 126 width: 64 127 height: 64 128 opacity: alphaSlider.value 129 scale: paletteMap.width / width; 130 layer.enabled: true 131 layer.smooth: true 132 anchors.centerIn: parent 133 property real hue: hueSlider.value 134 135 fragmentShader: content.OpenGLInfo.profile === OpenGLInfo.CoreProfile ? "#version 150 136 in vec2 qt_TexCoord0; 137 uniform float qt_Opacity; 138 uniform float hue; 139 out vec4 fragColor; 140 141 float hueToIntensity(float v1, float v2, float h) { 142 h = fract(h); 143 if (h < 1.0 / 6.0) 144 return v1 + (v2 - v1) * 6.0 * h; 145 else if (h < 1.0 / 2.0) 146 return v2; 147 else if (h < 2.0 / 3.0) 148 return v1 + (v2 - v1) * 6.0 * (2.0 / 3.0 - h); 149 150 return v1; 151 } 152 153 vec3 HSLtoRGB(vec3 color) { 154 float h = color.x; 155 float l = color.z; 156 float s = color.y; 157 158 if (s < 1.0 / 256.0) 159 return vec3(l, l, l); 160 161 float v1; 162 float v2; 163 if (l < 0.5) 164 v2 = l * (1.0 + s); 165 else 166 v2 = (l + s) - (s * l); 167 168 v1 = 2.0 * l - v2; 169 170 float d = 1.0 / 3.0; 171 float r = hueToIntensity(v1, v2, h + d); 172 float g = hueToIntensity(v1, v2, h); 173 float b = hueToIntensity(v1, v2, h - d); 174 return vec3(r, g, b); 175 } 176 177 void main() { 178 vec4 c = vec4(1.0); 179 c.rgb = HSLtoRGB(vec3(hue, 1.0 - qt_TexCoord0.t, qt_TexCoord0.s)); 180 fragColor = c * qt_Opacity; 181 } 182 " : " 183 varying mediump vec2 qt_TexCoord0; 184 uniform highp float qt_Opacity; 185 uniform highp float hue; 186 187 highp float hueToIntensity(highp float v1, highp float v2, highp float h) { 188 h = fract(h); 189 if (h < 1.0 / 6.0) 190 return v1 + (v2 - v1) * 6.0 * h; 191 else if (h < 1.0 / 2.0) 192 return v2; 193 else if (h < 2.0 / 3.0) 194 return v1 + (v2 - v1) * 6.0 * (2.0 / 3.0 - h); 195 196 return v1; 197 } 198 199 highp vec3 HSLtoRGB(highp vec3 color) { 200 highp float h = color.x; 201 highp float l = color.z; 202 highp float s = color.y; 203 204 if (s < 1.0 / 256.0) 205 return vec3(l, l, l); 206 207 highp float v1; 208 highp float v2; 209 if (l < 0.5) 210 v2 = l * (1.0 + s); 211 else 212 v2 = (l + s) - (s * l); 213 214 v1 = 2.0 * l - v2; 215 216 highp float d = 1.0 / 3.0; 217 highp float r = hueToIntensity(v1, v2, h + d); 218 highp float g = hueToIntensity(v1, v2, h); 219 highp float b = hueToIntensity(v1, v2, h - d); 220 return vec3(r, g, b); 221 } 222 223 void main() { 224 lowp vec4 c = vec4(1.0); 225 c.rgb = HSLtoRGB(vec3(hue, 1.0 - qt_TexCoord0.t, qt_TexCoord0.s)); 226 gl_FragColor = c * qt_Opacity; 227 } 228 " 229 } 230 231 MouseArea { 232 id: mapMouseArea 233 anchors.fill: parent 234 onPositionChanged: { 235 if (pressed && containsMouse) { 236 var xx = Math.max(0, Math.min(mouse.x, parent.width)) 237 var yy = Math.max(0, Math.min(mouse.y, parent.height)) 238 saturationSlider.value = 1.0 - yy / parent.height 239 lightnessSlider.value = xx / parent.width 240 // TODO if we constrain the movement here, can avoid the containsMouse test 241 crosshairs.x = mouse.x - crosshairs.radius 242 crosshairs.y = mouse.y - crosshairs.radius 243 } 244 } 245 onPressed: positionChanged(mouse) 246 } 247 248 Image { 249 id: crosshairs 250 property int radius: width / 2 // truncated to int 251 source: "images/crosshairs.png" 252 } 253 254 BorderImage { 255 anchors.fill: parent 256 anchors.margins: -1 257 anchors.leftMargin: -2 258 source: "images/sunken_frame.png" 259 border.left: 8 260 border.right: 8 261 border.top: 8 262 border.bottom: 8 263 } 264 } 265 } 266 267 Column { 268 id: sliders 269 anchors { 270 top: paletteFrame.bottom 271 left: parent.left 272 right: parent.right 273 margins: content.outerSpacing 274 } 275 276 ColorSlider { 277 id: hueSlider 278 value: 0.5 279 onValueChanged: if (__valueSet) root.currentColor = Qt.hsla(hueSlider.value, saturationSlider.value, lightnessSlider.value, alphaSlider.value) 280 text: qsTr("Hue") 281 trackDelegate: Rectangle { 282 rotation: -90 283 transformOrigin: Item.TopLeft 284 gradient: Gradient { 285 GradientStop {position: 0.000; color: Qt.rgba(1, 0, 0, 1)} 286 GradientStop {position: 0.167; color: Qt.rgba(1, 1, 0, 1)} 287 GradientStop {position: 0.333; color: Qt.rgba(0, 1, 0, 1)} 288 GradientStop {position: 0.500; color: Qt.rgba(0, 1, 1, 1)} 289 GradientStop {position: 0.667; color: Qt.rgba(0, 0, 1, 1)} 290 GradientStop {position: 0.833; color: Qt.rgba(1, 0, 1, 1)} 291 GradientStop {position: 1.000; color: Qt.rgba(1, 0, 0, 1)} 292 } 293 } 294 } 295 296 ColorSlider { 297 id: saturationSlider 298 visible: !content.usePaletteMap 299 value: 0.5 300 onValueChanged: if (__valueSet) root.currentColor = Qt.hsla(hueSlider.value, saturationSlider.value, lightnessSlider.value, alphaSlider.value) 301 text: qsTr("Saturation") 302 trackDelegate: Rectangle { 303 rotation: -90 304 transformOrigin: Item.TopLeft 305 gradient: Gradient { 306 GradientStop { position: 0; color: Qt.hsla(hueSlider.value, 0.0, lightnessSlider.value, 1.0) } 307 GradientStop { position: 1; color: Qt.hsla(hueSlider.value, 1.0, lightnessSlider.value, 1.0) } 308 } 309 } 310 } 311 312 ColorSlider { 313 id: lightnessSlider 314 visible: !content.usePaletteMap 315 value: 0.5 316 onValueChanged: if (__valueSet) root.currentColor = Qt.hsla(hueSlider.value, saturationSlider.value, lightnessSlider.value, alphaSlider.value) 317 text: qsTr("Luminosity") 318 trackDelegate: Rectangle { 319 rotation: -90 320 transformOrigin: Item.TopLeft 321 gradient: Gradient { 322 GradientStop { position: 0; color: "black" } 323 GradientStop { position: 0.5; color: Qt.hsla(hueSlider.value, saturationSlider.value, 0.5, 1.0) } 324 GradientStop { position: 1; color: "white" } 325 } 326 } 327 } 328 329 ColorSlider { 330 id: alphaSlider 331 minimum: 0.0 332 maximum: 1.0 333 value: 1.0 334 onValueChanged: if (__valueSet) root.currentColor = Qt.hsla(hueSlider.value, saturationSlider.value, lightnessSlider.value, alphaSlider.value) 335 text: qsTr("Alpha") 336 visible: root.showAlphaChannel 337 trackDelegate: Item { 338 rotation: -90 339 transformOrigin: Item.TopLeft 340 Image { 341 anchors {fill: parent} 342 source: "images/checkers.png" 343 fillMode: Image.TileVertically 344 } 345 Rectangle { 346 anchors.fill: parent 347 gradient: Gradient { 348 GradientStop { position: 0; color: "transparent" } 349 GradientStop { position: 1; color: Qt.hsla(hueSlider.value, 350 saturationSlider.value, 351 lightnessSlider.value, 1.0) } 352 } } 353 } 354 } 355 } 356 357 Item { 358 id: buttonRow 359 height: Math.max(buttonsOnly.height, copyIcon.height) 360 width: parent.width 361 anchors { 362 left: parent.left 363 right: parent.right 364 bottom: content.bottom 365 margins: content.outerSpacing 366 } 367 Row { 368 spacing: content.spacing 369 height: visible ? parent.height : 0 370 visible: !Settings.isMobile 371 TextField { 372 id: colorField 373 text: root.currentColor.toString() 374 anchors.verticalCenter: parent.verticalCenter 375 onAccepted: root.currentColor = text 376 Component.onCompleted: width = implicitWidth + 10 377 } 378 Image { 379 id: copyIcon 380 anchors.verticalCenter: parent.verticalCenter 381 source: "images/copy.png" 382 MouseArea { 383 anchors.fill: parent 384 onClicked: colorField.copyAll() 385 } 386 } 387 } 388 Row { 389 id: buttonsOnly 390 spacing: content.spacing 391 anchors.right: parent.right 392 Button { 393 id: cancelButton 394 text: qsTr("Cancel") 395 onClicked: root.reject() 396 } 397 Button { 398 id: okButton 399 text: qsTr("OK") 400 onClicked: root.accept() 401 } 402 } 403 } 404 } 405} 406