1/*
2 * Copyright (c) 2014-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 var defaultParameters: ['circle_radius','gaussian_radius', 'correlation', 'noise']
25    property bool blockUpdate: true
26    property var startValues:  [0.0, 0.0, 0.0,  0.0 ]
27    property var middleValues: [2.0, 0.0, 0.95, 0.01]
28    property var endValues:    [0.0, 0.0, 0.0,  0.0 ]
29    width: 350
30    height: 150
31    Component.onCompleted: {
32        if (filter.isNew) {
33            // Set default parameter values
34            filter.set('circle_radius', 2.0)
35            filter.set('gaussian_radius', 0.0)
36            filter.set('correlation', 0.95)
37            filter.set('noise', 0.01)
38            filter.savePreset(defaultParameters)
39        } else {
40            initSimpleAnimation()
41        }
42        setControls()
43    }
44
45    function initSimpleAnimation() {
46        middleValues = [filter.getDouble(defaultParameters[0], filter.animateIn),
47                        filter.getDouble(defaultParameters[1], filter.animateIn),
48                        filter.getDouble(defaultParameters[2], filter.animateIn),
49                        filter.getDouble(defaultParameters[3], filter.animateIn)]
50        if (filter.animateIn > 0) {
51            startValues = [filter.getDouble(defaultParameters[0], 0),
52                           filter.getDouble(defaultParameters[1], 0),
53                           filter.getDouble(defaultParameters[2], 0),
54                           filter.getDouble(defaultParameters[3], 0)]
55        }
56        if (filter.animateOut > 0) {
57            endValues = [filter.getDouble(defaultParameters[0], filter.duration - 1),
58                         filter.getDouble(defaultParameters[1], filter.duration - 1),
59                         filter.getDouble(defaultParameters[2], filter.duration - 1),
60                         filter.getDouble(defaultParameters[3], filter.duration - 1)]
61        }
62    }
63
64    function getPosition() {
65        return Math.max(producer.position - (filter.in - producer.in), 0)
66    }
67
68    function setControls() {
69        var position = getPosition()
70        blockUpdate = true
71        circleSlider.value = filter.getDouble("circle_radius", position)
72        circleKeyframesButton.checked = filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount('circle_radius') > 0
73        gaussianSlider.value = filter.getDouble("gaussian_radius", position)
74        gaussianKeyframesButton.checked = filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount('gaussian_radius') > 0
75        correlationSlider.value = filter.getDouble("correlation", position)
76        correlationKeyframesButton.checked = filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount('correlation') > 0
77        noiseSlider.value = filter.getDouble("noise", position)
78        noiseKeyframesButton.checked = filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount('noise') > 0
79        blockUpdate = false
80        circleSlider.enabled = gaussianSlider.enabled = correlationSlider.enabled = noiseSlider.enabled
81            = position <= 0 || (position >= (filter.animateIn - 1) && position <= (filter.duration - filter.animateOut)) || position >= (filter.duration - 1)
82    }
83
84    function updateFilter(parameter, value, position, button) {
85        if (blockUpdate) return
86        var index = defaultParameters.indexOf(parameter)
87
88        if (position !== null) {
89            if (position <= 0 && filter.animateIn > 0)
90                startValues[index] = value
91            else if (position >= filter.duration - 1 && filter.animateOut > 0)
92                endValues[index] = value
93            else
94                middleValues[index] = value
95        }
96
97        if (filter.animateIn > 0 || filter.animateOut > 0) {
98            filter.resetProperty(parameter)
99            button.checked = false
100            if (filter.animateIn > 0) {
101                filter.set(parameter, startValues[index], 0)
102                filter.set(parameter, middleValues[index], filter.animateIn - 1)
103            }
104            if (filter.animateOut > 0) {
105                filter.set(parameter, middleValues[index], filter.duration - filter.animateOut)
106                filter.set(parameter, endValues[index], filter.duration - 1)
107            }
108        } else if (!button.checked) {
109            filter.resetProperty(parameter)
110            filter.set(parameter, middleValues[index])
111        } else if (position !== null) {
112            filter.set(parameter, value, position)
113        }
114    }
115
116    function onKeyframesButtonClicked(checked, parameter, value) {
117        if (checked) {
118            blockUpdate = true
119            circleSlider.enabled = gaussianSlider.enabled = correlationSlider.enabled = noiseSlider.enabled = true
120            if (filter.animateIn > 0 || filter.animateOut > 0) {
121                for (var i = 0; i < defaultParameters.length; i++)
122                    filter.resetProperty(defaultParameters[i])
123                filter.animateIn = filter.animateOut = 0
124            } else {
125                filter.clearSimpleAnimation(parameter)
126            }
127            blockUpdate = false
128            filter.set(parameter, value, getPosition())
129        } else {
130            filter.resetProperty(parameter)
131            filter.set(parameter, value)
132        }
133    }
134
135    GridLayout {
136        columns: 4
137        anchors.fill: parent
138        anchors.margins: 8
139
140        Label {
141            text: qsTr('Preset')
142            Layout.alignment: Qt.AlignRight
143        }
144        Shotcut.Preset {
145            Layout.columnSpan: 3
146            parameters: defaultParameters
147            onBeforePresetLoaded: {
148                for (var i = 0; i < defaultParameters.length; i++)
149                    filter.resetProperty(defaultParameters[i])
150            }
151            onPresetSelected: {
152                setControls()
153                initSimpleAnimation()
154            }
155        }
156
157        // Row 2
158        Label {
159            text: qsTr('Circle radius')
160            Layout.alignment: Qt.AlignRight
161        }
162        Shotcut.SliderSpinner {
163            id: circleSlider
164            minimumValue: 0
165            maximumValue: 99.99
166            decimals: 2
167            stepSize: 0.1
168            onValueChanged: updateFilter('circle_radius', value, getPosition(), circleKeyframesButton)
169        }
170        Shotcut.UndoButton {
171            onClicked: circleSlider.value = 2
172        }
173        Shotcut.KeyframesButton {
174            id: circleKeyframesButton
175            onToggled: onKeyframesButtonClicked(checked, 'circle_radius', circleSlider.value)
176        }
177
178        // Row 3
179        Label {
180            text: qsTr('Gaussian radius')
181            Layout.alignment: Qt.AlignRight
182        }
183        Shotcut.SliderSpinner {
184            id: gaussianSlider
185            minimumValue: 0
186            maximumValue: 99.99
187            decimals: 2
188            stepSize: 0.1
189            onValueChanged: updateFilter('gaussian_radius', value, getPosition(), gaussianKeyframesButton)
190        }
191        Shotcut.UndoButton {
192            onClicked: gaussianSlider.value = 0
193        }
194        Shotcut.KeyframesButton {
195            id: gaussianKeyframesButton
196            onToggled: onKeyframesButtonClicked(checked, 'gaussian_radius', gaussianSlider.value)
197        }
198
199        // Row 4
200        Label {
201            text: qsTr('Correlation')
202            Layout.alignment: Qt.AlignRight
203        }
204        Shotcut.SliderSpinner {
205            id: correlationSlider
206            minimumValue: 0.0
207            maximumValue: 1.0
208            decimals: 2
209            onValueChanged: updateFilter('correlation', value, getPosition(), correlationKeyframesButton)
210        }
211        Shotcut.UndoButton {
212            onClicked: correlationSlider.value = 0.95
213        }
214        Shotcut.KeyframesButton {
215            id: correlationKeyframesButton
216            onToggled: onKeyframesButtonClicked(checked, 'correlation', correlationSlider.value)
217        }
218
219        // Row 5
220        Label {
221            text: qsTr('Noise')
222            Layout.alignment: Qt.AlignRight
223        }
224        Shotcut.SliderSpinner {
225            id: noiseSlider
226            minimumValue: 0.0
227            maximumValue: 1.0
228            decimals: 2
229            onValueChanged: updateFilter('noise', value, getPosition(), noiseKeyframesButton)
230        }
231        Shotcut.UndoButton {
232            onClicked: noiseSlider.value = 0.01
233        }
234        Shotcut.KeyframesButton {
235            id: noiseKeyframesButton
236            onToggled: onKeyframesButtonClicked(checked, 'noise', noiseSlider.value)
237        }
238
239        Item {
240            Layout.fillHeight: true
241        }
242    }
243
244    function updateSimpleAnimation() {
245        updateFilter('circle_radius', circleSlider.value, getPosition(), circleKeyframesButton)
246        updateFilter('gaussian_radius', gaussianSlider.value, getPosition(), gaussianKeyframesButton)
247        updateFilter('correlation', correlationSlider.value, getPosition(), correlationKeyframesButton)
248        updateFilter('noise', noiseSlider.value, getPosition(), noiseKeyframesButton)
249    }
250
251    Connections {
252        target: filter
253        onInChanged: updateSimpleAnimation()
254        onOutChanged: updateSimpleAnimation()
255        onAnimateInChanged: updateSimpleAnimation()
256        onAnimateOutChanged: updateSimpleAnimation()
257    }
258
259    Connections {
260        target: producer
261        onPositionChanged: setControls()
262    }
263}
264