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