1/****************************************************************************
2**
3** Copyright (C) 2021 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of Qt Creator.
7**
8** Commercial License Usage
9** Licensees holding valid commercial Qt licenses may use this file in
10** accordance with the commercial license agreement provided with the
11** Software or, alternatively, in accordance with the terms contained in
12** a written agreement between you and The Qt Company. For licensing terms
13** and conditions see https://www.qt.io/terms-conditions. For further
14** information use the contact form at https://www.qt.io/contact-us.
15**
16** GNU General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU
18** General Public License version 3 as published by the Free Software
19** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20** included in the packaging of this file. Please review the following
21** information to ensure the GNU General Public License requirements will
22** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23**
24****************************************************************************/
25
26import QtQuick 2.15
27import QtQuick.Shapes 1.15
28import QtQuick.Templates 2.15 as T
29import StudioTheme 1.0 as StudioTheme
30
31Rectangle {
32    id: linkIndicator
33
34    property Item myControl
35
36    property bool linked: linkXZ.linked || linkYZ.linked || linkXY.linked
37
38    color: "transparent"
39    border.color: "transparent"
40
41    implicitWidth: StudioTheme.Values.height
42    implicitHeight: StudioTheme.Values.height
43
44    z: 10
45
46    function linkAll() {
47        linkXY.linked = linkYZ.linked = linkXZ.linked = true
48    }
49
50    function unlinkAll() {
51        linkXY.linked = linkYZ.linked = linkXZ.linked = false
52    }
53
54    T.Label {
55        id: linkIndicatorIcon
56        anchors.fill: parent
57        text: linkIndicator.linked ? StudioTheme.Constants.linked
58                                   : StudioTheme.Constants.unLinked
59        visible: true
60        color: StudioTheme.Values.themeTextColor
61        font.family: StudioTheme.Constants.iconFont.family
62        font.pixelSize: StudioTheme.Values.myIconFontSize
63        verticalAlignment: Text.AlignVCenter
64        horizontalAlignment: Text.AlignHCenter
65    }
66
67    MouseArea {
68        id: mouseArea
69        anchors.fill: parent
70        hoverEnabled: true
71        onPressed: linkPopup.opened ? linkPopup.close() : linkPopup.open()
72    }
73
74    T.Popup {
75        id: linkPopup
76
77        x: 50
78        y: 0
79
80        // TODO proper size
81        width: 20 + (3 * StudioTheme.Values.height)
82        height: 20 + (3 * StudioTheme.Values.height)
83
84        padding: StudioTheme.Values.border
85        margins: 0 // If not defined margin will be -1
86
87        closePolicy: T.Popup.CloseOnPressOutside | T.Popup.CloseOnPressOutsideParent
88                     | T.Popup.CloseOnEscape | T.Popup.CloseOnReleaseOutside
89                     | T.Popup.CloseOnReleaseOutsideParent
90
91        contentItem: Item {
92            id: content
93            anchors.fill: parent
94
95            Rectangle {
96                width: triangle.diameter
97                height: triangle.diameter
98                anchors.centerIn: parent
99                color: "transparent"
100
101                Shape {
102                    // Kept for debugging purposes
103                    id: triangle
104
105                    visible: false
106
107                    property real diameter: 30 * StudioTheme.Values.scaleFactor
108                    property real radius: triangle.diameter * 0.5
109
110                    width: triangle.diameter
111                    height: triangle.diameter
112
113                    ShapePath {
114                        id: path
115
116                        property real height: triangle.diameter * Math.cos(Math.PI / 6)
117
118                        property vector2d pX: Qt.vector2d(triangle.radius, 0)
119                        property vector2d pY: Qt.vector2d(0, path.height)
120                        property vector2d pZ: Qt.vector2d(triangle.diameter, path.height)
121
122                        property vector2d center: Qt.vector2d(triangle.radius, triangle.radius)
123
124                        strokeWidth: StudioTheme.Values.border
125                        strokeColor: triangleMouseArea.containsMouse ? "white" : "gray"
126                        strokeStyle: ShapePath.SolidLine
127                        fillColor: "transparent"
128
129                        startX: path.pX.x
130                        startY: path.pX.y
131
132                        PathLine { x: path.pY.x; y: path.pY.y }
133                        PathLine { x: path.pZ.x; y: path.pZ.y }
134                        PathLine { x: path.pX.x; y: path.pX.y }
135                    }
136                }
137
138                Item {
139                    id: triangleMouseArea
140
141                    anchors.fill: parent
142
143                    property alias mouseX: tmpMouseArea.mouseX
144                    property alias mouseY: tmpMouseArea.mouseY
145
146                    signal clicked
147
148                    function sign(p1, p2, p3) {
149                        return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y)
150                    }
151
152                    function pointInTriangle(pt, v1, v2, v3) {
153                        var d1 = sign(pt, v1, v2)
154                        var d2 = sign(pt, v2, v3)
155                        var d3 = sign(pt, v3, v1)
156
157                        var has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0)
158                        var has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0)
159
160                        return !(has_neg && has_pos)
161                    }
162
163                    property bool containsMouse: {
164                        if (!tmpMouseArea.containsMouse)
165                            return false
166
167                        var point = Qt.vector2d(triangleMouseArea.mouseX, triangleMouseArea.mouseY)
168                        return pointInTriangle(point, path.pX, path.pZ, path.pY)
169                    }
170
171
172                    onClicked: {
173                        if (linkXZ.linked && linkYZ.linked && linkXY.linked)
174                            linkIndicator.unlinkAll()
175                        else
176                            linkIndicator.linkAll()
177                    }
178
179                    MouseArea {
180                        id: tmpMouseArea
181                        anchors.fill: parent
182                        hoverEnabled: true
183                        acceptedButtons: Qt.LeftButton | Qt.RightButton
184                        onClicked: {
185                            if (triangleMouseArea.containsMouse)
186                                triangleMouseArea.clicked()
187                        }
188                    }
189                }
190
191                // https://stackoverflow.com/questions/38164074/how-to-create-a-round-mouse-area-in-qml
192                // https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle
193
194                T.Label {
195                    id: triangleIcon
196                    anchors.fill: parent
197                    anchors.bottomMargin: 3 * StudioTheme.Values.scaleFactor
198                    text: StudioTheme.Constants.linkTriangle
199                    visible: true
200                    color: triangleMouseArea.containsMouse ? "white" : "gray"
201                    font.family: StudioTheme.Constants.iconFont.family
202                    font.pixelSize: StudioTheme.Values.myIconFontSize * 3
203                    verticalAlignment: Text.AlignVCenter
204                    horizontalAlignment: Text.AlignHCenter
205                }
206
207                LinkIndicator3DComponent {
208                    id: linkXZ
209                    pointA: path.pX
210                    pointB: path.pZ
211                    rotation: 105 // 60
212                }
213
214                LinkIndicator3DComponent {
215                    id: linkYZ
216                    pointA: path.pZ
217                    pointB: path.pY
218                    rotation: 45 // -180
219                }
220
221                LinkIndicator3DComponent {
222                    id: linkXY
223                    pointA: path.pY
224                    pointB: path.pX
225                    rotation: -15 // -60
226                }
227
228                T.Label {
229                    id: xIcon
230                    x: path.pX.x - (xIcon.width * 0.5)
231                    y: path.pX.y - xIcon.height
232                    text: "X"
233                    color: StudioTheme.Values.theme3DAxisXColor
234
235                    font.family: StudioTheme.Constants.font.family
236                    font.pixelSize: StudioTheme.Values.myFontSize
237                    verticalAlignment: Text.AlignVCenter
238                    horizontalAlignment: Text.AlignHCenter
239                }
240                T.Label {
241                    id: yIcon
242                    x: path.pY.x - yIcon.width - (4.0 * StudioTheme.Values.scaleFactor)
243                    y: path.pY.y - (yIcon.height * 0.5)
244                    text: "Y"
245                    color: StudioTheme.Values.theme3DAxisYColor
246
247                    font.family: StudioTheme.Constants.font.family
248                    font.pixelSize: StudioTheme.Values.myFontSize
249                    verticalAlignment: Text.AlignVCenter
250                    horizontalAlignment: Text.AlignHCenter
251                }
252                T.Label {
253                    id: zIcon
254                    x: path.pZ.x + (4.0 * StudioTheme.Values.scaleFactor)
255                    y: path.pZ.y - (zIcon.height * 0.5)
256                    text: "Z"
257                    color: StudioTheme.Values.theme3DAxisZColor
258
259                    font.family: StudioTheme.Constants.font.family
260                    font.pixelSize: StudioTheme.Values.myFontSize
261                    verticalAlignment: Text.AlignVCenter
262                    horizontalAlignment: Text.AlignHCenter
263                }
264
265            }
266        }
267
268        background: Rectangle {
269            color: StudioTheme.Values.themeControlBackground
270            border.color: StudioTheme.Values.themeInteraction
271            border.width: StudioTheme.Values.border
272        }
273
274        enter: Transition {}
275        exit: Transition {}
276    }
277
278    states: [
279        State {
280            name: "default"
281            when: !mouseArea.containsMouse && !linkPopup.opened
282            PropertyChanges {
283                target: linkIndicatorIcon
284                color: StudioTheme.Values.themeLinkIndicatorColor
285            }
286        },
287        State {
288            name: "hover"
289            when: mouseArea.containsMouse && !linkPopup.opened
290            PropertyChanges {
291                target: linkIndicatorIcon
292                color: StudioTheme.Values.themeLinkIndicatorColorHover
293            }
294        },
295        State {
296            name: "active"
297            when: linkPopup.opened
298            PropertyChanges {
299                target: linkIndicatorIcon
300                color: StudioTheme.Values.themeLinkIndicatorColorInteraction
301            }
302        }
303    ]
304}
305