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