1/*
2 * Copyright (c) 2013-2021 Meltytech, LLC
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18import QtQuick 2.12
19import QtQuick.Controls 2.12
20import QtQuick.Layouts 1.12
21import Shotcut.Controls 1.0 as Shotcut
22
23Shotcut.KeyframableFilter {
24    width: 350
25    height: 180
26    property bool isAtLeastVersion4: filter.isAtLeastVersion('4')
27
28    keyframableParameters: ['transition.fix_rotate_x', 'transition.scale_x', 'transition.scale_y']
29    startValues: [0.0, 1.0, 1.0]
30    middleValues: [0.0, 1.0, 1.0]
31    endValues: [0.0, 1.0, 1.0]
32
33    Component.onCompleted: {
34        if (isAtLeastVersion4 && filter.get('transition.invert_scale') != 1) {
35            var scale = filter.getDouble('transition.scale_x')
36            if (scale !== 0.0)
37                filter.set('transition.scale_x', 1.0 / scale)
38            scale = filter.getDouble('transition.scale_y')
39            if (scale !== 0.0)
40                filter.set('transition.scale_y', 1.0 / scale)
41            filter.set('transition.invert_scale', 1)
42        }
43        if (filter.isNew) {
44            // Set default parameter values
45            filter.set('transition.fix_rotate_x', 0)
46            filter.set('transition.scale_x', 1)
47            filter.set('transition.scale_y', 1)
48            filter.set('transition.ox', 0)
49            filter.set('transition.oy', 0)
50            filter.set('transition.threads', 0)
51            filter.set('background', 'color:#00000000')
52            filter.savePreset(preset.parameters)
53        } else {
54            initializeSimpleKeyframes()
55        }
56        setControls()
57    }
58
59    function setControls() {
60        var position = getPosition()
61        blockUpdate = true
62        rotationSlider.value = filter.getDouble('transition.fix_rotate_x', position)
63        rotationSlider.enabled = scaleSlider.enabled = isSimpleKeyframesActive()
64        rotationKeyframesButton.checked = filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount('transition.fix_rotate_x') > 0
65        var scale = filter.getDouble('transition.scale_x', position)
66        scaleSlider.value = isAtLeastVersion4? scale * 100 : 100 / scale
67        scaleKeyframesButton.checked = filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount('transition.scale_x') > 0
68        xOffsetSlider.value = filter.getDouble('transition.ox', position) * -1
69        xOffsetKeyframesButton.checked = filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount('transition.ox') > 0
70        yOffsetSlider.value = filter.getDouble('transition.oy', position) * -1
71        yOffsetKeyframesButton.checked = filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount('transition.oy') > 0
72        blockUpdate = false
73
74        var s = filter.get('background')
75        if (s.substring(0, 6) === 'color:')
76            bgColor.value = s.substring(6)
77        else if  (s.substring(0, 7) === 'colour:')
78            bgColor.value = s.substring(7)
79    }
80
81    function getScaleValue() {
82        if (isAtLeastVersion4) {
83            return scaleSlider.value / 100
84        } else {
85            return 100 / scaleSlider.value
86        }
87    }
88
89    function updateFilterScale(position) {
90        if (blockUpdate) return
91        var value = getScaleValue()
92        var index = 1
93
94        if (position !== null) {
95            if (position <= 0 && filter.animateIn > 0)
96                startValues[index] = value
97            else if (position >= filter.duration - 1 && filter.animateOut > 0)
98                endValues[index] = value
99            else
100                middleValues[index] = value
101        }
102
103        if (filter.animateIn > 0 || filter.animateOut > 0) {
104            filter.resetProperty('transition.scale_x')
105            filter.resetProperty('transition.scale_y')
106            scaleKeyframesButton.checked = false
107            if (filter.animateIn > 0) {
108                filter.set('transition.scale_x', startValues[index], 0)
109                filter.set('transition.scale_x', middleValues[index], filter.animateIn - 1)
110                filter.set('transition.scale_y', startValues[index], 0)
111                filter.set('transition.scale_y', middleValues[index], filter.animateIn - 1)
112            }
113            if (filter.animateOut > 0) {
114                filter.set('transition.scale_x', middleValues[index], filter.duration - filter.animateOut)
115                filter.set('transition.scale_x', endValues[index], filter.duration - 1)
116                filter.set('transition.scale_y', middleValues[index], filter.duration - filter.animateOut)
117                filter.set('transition.scale_y', endValues[index], filter.duration - 1)
118            }
119        } else if (!scaleKeyframesButton.checked) {
120            filter.resetProperty('transition.scale_x')
121            filter.set('transition.scale_x', middleValues[index])
122            filter.resetProperty('transition.scale_y')
123            filter.set('transition.scale_y', middleValues[index])
124        } else if (position !== null) {
125            filter.set('transition.scale_x', value, position)
126            filter.set('transition.scale_y', value, position)
127        }
128    }
129
130    function updateSimpleKeyframes() {
131        updateFilter('transition.fix_rotate_x', rotationSlider.value, rotationKeyframesButton)
132        updateFilterScale(null)
133    }
134
135    GridLayout {
136        anchors.fill: parent
137        anchors.margins: 8
138        columns: 4
139
140        Label {
141            text: qsTr('Preset')
142            Layout.alignment: Qt.AlignRight
143        }
144        Shotcut.Preset {
145            id: preset
146            parameters: ['transition.fix_rotate_x', 'transition.scale_x', 'transition.ox', 'transition.oy', 'background']
147            Layout.columnSpan: 3
148            onBeforePresetLoaded: {
149                resetSimpleKeyframes()
150                filter.resetProperty('transition.ox')
151                filter.resetProperty('transition.oy')
152            }
153            onPresetSelected: {
154                filter.set('transition.scale_y', filter.get('transition.scale_x'))
155                setControls()
156                initializeSimpleKeyframes()
157            }
158        }
159
160        Label {
161            text: qsTr('Rotation')
162            Layout.alignment: Qt.AlignRight
163        }
164        Shotcut.SliderSpinner {
165            id: rotationSlider
166            minimumValue: -360
167            maximumValue: 360
168            decimals: 1
169            spinnerWidth: 110
170            suffix: qsTr(' deg', 'degrees')
171            onValueChanged: updateFilter('transition.fix_rotate_x', value, rotationKeyframesButton, getPosition())
172        }
173        Shotcut.UndoButton {
174            onClicked: rotationSlider.value = 0
175        }
176        Shotcut.KeyframesButton {
177            id: rotationKeyframesButton
178            onToggled: {
179                toggleKeyframes(checked, 'transition.fix_rotate_x', rotationSlider.value)
180                setControls()
181            }
182        }
183
184        Label {
185            text: qsTr('Scale')
186            Layout.alignment: Qt.AlignRight
187        }
188        Shotcut.SliderSpinner {
189            id: scaleSlider
190            minimumValue: 0.1
191            maximumValue: 1000
192            decimals: 1
193            spinnerWidth: 110
194            suffix: ' %'
195            onValueChanged: updateFilterScale(getPosition())
196        }
197        Shotcut.UndoButton {
198            onClicked: scaleSlider.value = 100
199        }
200        Shotcut.KeyframesButton {
201            id: scaleKeyframesButton
202            onToggled: {
203                var value = getScaleValue()
204                if (checked) {
205                    blockUpdate = true
206                    if (filter.animateIn > 0 || filter.animateOut > 0) {
207                        resetSimpleKeyframes()
208                        filter.animateIn = filter.animateOut = 0
209                    } else {
210                        filter.clearSimpleAnimation('transition.scale_x')
211                        filter.clearSimpleAnimation('transition.scale_y')
212                    }
213                    blockUpdate = false
214                    filter.set('transition.scale_x', value, getPosition())
215                    filter.set('transition.scale_y', value, getPosition())
216                } else {
217                    filter.resetProperty('transition.scale_x')
218                    filter.resetProperty('transition.scale_y')
219                    filter.set('transition.scale_x', value)
220                    filter.set('transition.scale_y', value)
221                }
222                setControls()
223            }
224        }
225
226        Label {
227            text: qsTr('X offset')
228            Layout.alignment: Qt.AlignRight
229        }
230        Shotcut.SliderSpinner {
231            id: xOffsetSlider
232            minimumValue: -5000
233            maximumValue: 5000
234            spinnerWidth: 110
235            onValueChanged: if (!blockUpdate) {
236                if (xOffsetKeyframesButton.checked)
237                    filter.set('transition.ox', -value, getPosition())
238                else
239                    filter.set('transition.ox', -value)
240            }
241        }
242        Shotcut.UndoButton {
243            onClicked: xOffsetSlider.value = 0
244        }
245        Shotcut.KeyframesButton {
246            id: xOffsetKeyframesButton
247            onToggled: {
248                if (checked) {
249                    filter.set('transition.ox', -xOffsetSlider.value, getPosition())
250                } else {
251                    filter.resetProperty('transition.ox')
252                    filter.set('transition.ox', -xOffsetSlider.value)
253                }
254            }
255        }
256
257        Label {
258            text: qsTr('Y offset')
259            Layout.alignment: Qt.AlignRight
260        }
261        Shotcut.SliderSpinner {
262            id: yOffsetSlider
263            minimumValue: -5000
264            maximumValue: 5000
265            spinnerWidth: 110
266            onValueChanged: if (!blockUpdate) {
267                if (yOffsetKeyframesButton.checked)
268                    filter.set('transition.oy', -value, getPosition())
269                else
270                    filter.set('transition.oy', -value)
271            }
272        }
273        Shotcut.UndoButton {
274            onClicked: yOffsetSlider.value = 0
275        }
276        Shotcut.KeyframesButton {
277            id: yOffsetKeyframesButton
278            onToggled: {
279                if (checked) {
280                    filter.set('transition.oy', -yOffsetSlider.value, getPosition())
281                } else {
282                    filter.resetProperty('transition.oy')
283                    filter.set('transition.oy', -yOffsetSlider.value)
284                }
285            }
286        }
287
288        Label {
289            text: qsTr('Background color')
290            Layout.alignment: Qt.AlignRight
291        }
292        Shotcut.ColorPicker {
293            id: bgColor
294            eyedropper: true
295            alpha: true
296            onValueChanged: filter.set('background', 'color:' + value)
297        }
298        Shotcut.UndoButton {
299            visible: bgColor.visible
300            onClicked: bgColor.value = '#00000000'
301        }
302        Item {
303            Layout.fillWidth: true
304        }
305
306        Item {
307            Layout.fillHeight: true
308        }
309    }
310
311    Connections {
312        target: filter
313        onInChanged: updateSimpleKeyframes()
314        onOutChanged: updateSimpleKeyframes()
315        onAnimateInChanged: updateSimpleKeyframes()
316        onAnimateOutChanged: updateSimpleKeyframes()
317    }
318
319    Connections {
320        target: producer
321        onPositionChanged: setControls()
322    }
323
324    Connections {
325        target: parameters
326        onKeyframeAdded: {
327            var n = filter.getDouble(parameter, position)
328            filter.set(parameter, n, position)
329        }
330    }
331}
332