1/*
2 * Copyright (c) 2017-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
23Item {
24    property string paramShape: 'filter.0'
25    property string paramHorizontal: 'filter.1'
26    property string paramVertical: 'filter.2'
27    property string paramWidth: 'filter.3'
28    property string paramHeight: 'filter.4'
29    property string paramRotation: 'filter.5'
30    property string paramSoftness: 'filter.6'
31    property string paramOperation: 'filter.9'
32    property var defaultParameters: [paramHorizontal, paramVertical, paramWidth, paramHeight,  paramShape, paramRotation, paramSoftness, paramOperation]
33    property bool blockUpdate: true
34    property var startValues: [0.5, 0.5, 0.1, 0.1]
35    property var middleValues: [0.5, 0.5, 0.1, 0.1]
36    property var endValues: [0.5, 0.5, 0.1, 0.1]
37
38    width: 350
39    height: 250
40
41    Component.onCompleted: {
42        if (filter.isNew) {
43            // Set default parameter values
44            filter.set('filter', 'frei0r.alphaspot')
45            filter.set(paramOperation, 0)
46            filter.set(paramShape, 0)
47            filter.set(paramHorizontal, 0.5)
48            filter.set(paramVertical, 0.5)
49            filter.set(paramWidth, 0.1)
50            filter.set(paramHeight, 0.1)
51            filter.set(paramRotation, 0.5)
52            filter.set(paramSoftness, 0.2)
53            filter.savePreset(defaultParameters)
54        } else {
55            initSimpleAnimation()
56        }
57        setControls()
58    }
59
60    function initSimpleAnimation() {
61        middleValues = [filter.getDouble(paramHorizontal, filter.animateIn),
62                        filter.getDouble(paramVertical, filter.animateIn),
63                        filter.getDouble(paramWidth, filter.animateIn),
64                        filter.getDouble(paramHeight, filter.animateIn)]
65        if (filter.animateIn > 0) {
66            startValues = [filter.getDouble(paramHorizontal, 0),
67                           filter.getDouble(paramVertical, 0),
68                           filter.getDouble(paramWidth, 0),
69                           filter.getDouble(paramHeight, 0)]
70        }
71        if (filter.animateOut > 0) {
72            endValues = [filter.getDouble(paramHorizontal, filter.duration - 1),
73                         filter.getDouble(paramVertical, filter.duration - 1),
74                         filter.getDouble(paramWidth, filter.duration - 1),
75                         filter.getDouble(paramHeight, filter.duration - 1)]
76        }
77    }
78
79    function getPosition() {
80        return Math.max(producer.position - (filter.in - producer.in), 0)
81    }
82
83    function setControls() {
84        var position = getPosition()
85        blockUpdate = true
86        horizontalSlider.value = filter.getDouble(paramHorizontal, position) * 100
87        horizontalKeyframesButton.checked = filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount(paramHorizontal) > 0
88        verticalSlider.value   = filter.getDouble(paramVertical, position) * 100
89        verticalKeyframesButton.checked = filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount(paramVertical) > 0
90        widthSlider.value      = filter.getDouble(paramWidth, position) * 100
91        widthKeyframesButton.checked = filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount(paramWidth) > 0
92        heightSlider.value     = filter.getDouble(paramHeight, position) * 100
93        heightKeyframesButton.checked = filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount(paramHeight) > 0
94        blockUpdate = false
95        horizontalSlider.enabled = verticalSlider.enabled = widthSlider.enabled = heightSlider.enabled
96            = position <= 0 || (position >= (filter.animateIn - 1) && position <= (filter.duration - filter.animateOut)) || position >= (filter.duration - 1)
97        operationCombo.currentIndex = Math.round(filter.getDouble(paramOperation) * 4)
98        shapeCombo.currentIndex = Math.round(filter.getDouble(paramShape) * 3)
99        rotationSlider.value = (filter.getDouble(paramRotation) - 0.5) * 360
100        softnessSlider.value = filter.getDouble(paramSoftness) * 100
101    }
102
103    function updateFilter(parameter, value, position, button) {
104        if (blockUpdate) return
105        var index = defaultParameters.indexOf(parameter)
106
107        if (position !== null) {
108            if (position <= 0 && filter.animateIn > 0)
109                startValues[index] = value
110            else if (position >= filter.duration - 1 && filter.animateOut > 0)
111                endValues[index] = value
112            else
113                middleValues[index] = value
114        }
115
116        if (filter.animateIn > 0 || filter.animateOut > 0) {
117            filter.resetProperty(parameter)
118            button.checked = false
119            if (filter.animateIn > 0) {
120                filter.set(parameter, startValues[index], 0)
121                filter.set(parameter, middleValues[index], filter.animateIn - 1)
122            }
123            if (filter.animateOut > 0) {
124                filter.set(parameter, middleValues[index], filter.duration - filter.animateOut)
125                filter.set(parameter, endValues[index], filter.duration - 1)
126            }
127        } else if (!button.checked) {
128            filter.resetProperty(parameter)
129            filter.set(parameter, middleValues[index])
130        } else if (position !== null) {
131            filter.set(parameter, value, position)
132        }
133    }
134
135    function onKeyframesButtonClicked(checked, parameter, value) {
136        if (checked) {
137            blockUpdate = true
138            horizontalSlider.enabled = verticalSlider.enabled = widthSlider.enabled = heightSlider.enabled = true
139            if (filter.animateIn > 0 || filter.animateOut > 0) {
140                filter.resetProperty(paramHorizontal)
141                filter.resetProperty(paramVertical)
142                filter.resetProperty(paramWidth)
143                filter.resetProperty(paramHeight)
144                filter.animateIn = filter.animateOut = 0
145            } else {
146                filter.clearSimpleAnimation(parameter)
147            }
148            blockUpdate = false
149            filter.set(parameter, value, getPosition())
150        } else {
151            filter.resetProperty(parameter)
152            filter.set(parameter, value)
153        }
154    }
155
156    GridLayout {
157        columns: 4
158        anchors.fill: parent
159        anchors.margins: 8
160
161        Label {
162            text: qsTr('Preset')
163            Layout.alignment: Qt.AlignRight
164        }
165        Shotcut.Preset {
166            Layout.columnSpan: 3
167            parameters: defaultParameters
168            onBeforePresetLoaded: {
169                filter.resetProperty(paramHorizontal)
170                filter.resetProperty(paramVertical)
171                filter.resetProperty(paramWidth)
172                filter.resetProperty(paramHeight)
173            }
174            onPresetSelected: {
175                setControls()
176                initSimpleAnimation()
177            }
178        }
179
180        Label {
181            text: qsTr('Operation')
182            Layout.alignment: Qt.AlignRight
183        }
184        Shotcut.ComboBox {
185            id: operationCombo
186            implicitWidth: 180
187            model: [qsTr('Overwrite'), qsTr('Maximum'), qsTr('Minimum'), qsTr('Add'), qsTr('Subtract')]
188            onActivated: filter.set(paramOperation, currentIndex / 4)
189        }
190        Shotcut.UndoButton {
191            Layout.columnSpan: 2
192            onClicked: {
193                filter.set(paramOperation, 0)
194                operationCombo.currentIndex = 0
195            }
196        }
197
198        Label {
199            text: qsTr('Shape')
200            Layout.alignment: Qt.AlignRight
201        }
202        Shotcut.ComboBox {
203            id: shapeCombo
204            implicitWidth: 180
205            model: [qsTr('Rectangle'), qsTr('Ellipse'), qsTr('Triangle'), qsTr('Diamond')]
206            onActivated: filter.set(paramShape, currentIndex / 3)
207        }
208        Shotcut.UndoButton {
209            Layout.columnSpan: 2
210            onClicked:  {
211                filter.set(paramShape, 0)
212                shapeCombo.currentIndex = 0
213            }
214        }
215
216        Label {
217            text: qsTr('Horizontal')
218            Layout.alignment: Qt.AlignRight
219        }
220        Shotcut.SliderSpinner {
221            id: horizontalSlider
222            minimumValue: -100
223            maximumValue: 100
224            decimals: 2
225            suffix: ' %'
226            onValueChanged: updateFilter(paramHorizontal, value/100, getPosition(), horizontalKeyframesButton)
227        }
228        Shotcut.UndoButton {
229            onClicked: horizontalSlider.value = 50
230        }
231        Shotcut.KeyframesButton {
232            id: horizontalKeyframesButton
233            onToggled: onKeyframesButtonClicked(checked, paramHorizontal, horizontalSlider.value / 100)
234        }
235
236        Label {
237            text: qsTr('Vertical')
238            Layout.alignment: Qt.AlignRight
239        }
240        Shotcut.SliderSpinner {
241            id: verticalSlider
242            minimumValue: -100
243            maximumValue: 100
244            decimals: 2
245            suffix: ' %'
246            onValueChanged: updateFilter(paramVertical, value/100, getPosition(), verticalKeyframesButton)
247        }
248        Shotcut.UndoButton {
249            onClicked: verticalSlider.value = 50
250        }
251        Shotcut.KeyframesButton {
252            id: verticalKeyframesButton
253            onToggled: onKeyframesButtonClicked(checked, paramVertical, verticalSlider.value / 100)
254        }
255
256        Label {
257            text: qsTr('Width')
258            Layout.alignment: Qt.AlignRight
259        }
260        Shotcut.SliderSpinner {
261            id: widthSlider
262            minimumValue: 0.0001
263            maximumValue: 100
264            decimals: 2
265            suffix: ' %'
266            onValueChanged: updateFilter(paramWidth, value/100, getPosition(), widthKeyframesButton)
267        }
268        Shotcut.UndoButton {
269            onClicked: widthSlider.value = 10
270        }
271        Shotcut.KeyframesButton {
272            id: widthKeyframesButton
273            onToggled: onKeyframesButtonClicked(checked, paramWidth, widthSlider.value / 100)
274        }
275
276        Label {
277            text: qsTr('Height')
278            Layout.alignment: Qt.AlignRight
279        }
280        Shotcut.SliderSpinner {
281            id: heightSlider
282            minimumValue: 0.0001
283            maximumValue: 100
284            decimals: 2
285            suffix: ' %'
286            onValueChanged: updateFilter(paramHeight, value/100, getPosition(), heightKeyframesButton)
287        }
288        Shotcut.UndoButton {
289            onClicked: heightSlider.value = 10
290        }
291        Shotcut.KeyframesButton {
292            id: heightKeyframesButton
293            onToggled: onKeyframesButtonClicked(checked, paramHeight, heightSlider.value / 100)
294        }
295
296        Label {
297            text: qsTr('Rotation')
298            Layout.alignment: Qt.AlignRight
299        }
300        Shotcut.SliderSpinner {
301            id: rotationSlider
302            minimumValue: -179.9
303            maximumValue: 179.9
304            decimals: 1
305            spinnerWidth: 110
306            suffix: qsTr(' deg', 'degrees')
307            onValueChanged: filter.set(paramRotation, 0.5 + value / 360)
308        }
309        Shotcut.UndoButton {
310            Layout.columnSpan: 2
311            onClicked: rotationSlider.value = 0
312        }
313
314        Label {
315            text: qsTr('Softness')
316            Layout.alignment: Qt.AlignRight
317        }
318        Shotcut.SliderSpinner {
319            id: softnessSlider
320            minimumValue: 0
321            maximumValue: 100
322            decimals: 2
323            suffix: ' %'
324            onValueChanged: filter.set(paramSoftness, value / 100)
325        }
326        Shotcut.UndoButton {
327            Layout.columnSpan: 2
328            onClicked: softnessSlider.value = 20
329        }
330
331        Item {
332            Layout.columnSpan: 2
333            Layout.fillHeight: true;
334        }
335    }
336
337    function updatedSimpleAnimation() {
338        updateFilter(paramHorizontal, horizontalSlider.value/100, getPosition(), horizontalKeyframesButton)
339        updateFilter(paramVertical,   verticalSlider.value/100,   getPosition(), verticalKeyframesButton)
340        updateFilter(paramWidth,      widthSlider.value/100,      getPosition(), widthKeyframesButton)
341        updateFilter(paramHeight,     heightSlider.value/100,     getPosition(), heightKeyframesButton)
342    }
343
344    Connections {
345        target: filter
346        onInChanged: updatedSimpleAnimation()
347        onOutChanged: updatedSimpleAnimation()
348        onAnimateInChanged: updatedSimpleAnimation()
349        onAnimateOutChanged: updatedSimpleAnimation()
350    }
351
352    Connections {
353        target: producer
354        onPositionChanged: {
355            if (filter.animateIn > 0 || filter.animateOut > 0) {
356                setControls()
357            } else {
358                blockUpdate = true
359                horizontalSlider.value = filter.getDouble(paramHorizontal, getPosition()) * 100
360                verticalSlider.value   = filter.getDouble(paramVertical,   getPosition()) * 100
361                widthSlider.value      = filter.getDouble(paramWidth,      getPosition()) * 100
362                heightSlider.value     = filter.getDouble(paramHeight,     getPosition()) * 100
363                blockUpdate = false
364                horizontalSlider.enabled = verticalSlider.enabled = widthSlider.enabled = heightSlider.enabled = true
365            }
366        }
367    }
368}
369