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