Warning, /multimedia/kdenlive/src/timeline2/view/qml/KeyframeView.qml is written in an unsupported language. File is not indexed.

0001 /*
0002     SPDX-FileCopyrightText: 2017 Jean-Baptiste Mardelle
0003     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 import QtQuick 2.15
0007 import QtQuick.Controls 2.15
0008 import QtQml.Models 2.15
0009 import com.enums 1.0
0010 
0011 Rectangle
0012 {
0013     id: keyframeContainer
0014     property int kfrCount : keyframes.count
0015     anchors.fill: parent
0016     color: Qt.rgba(1,1,0.8, 0.3)
0017     property int activeIndex
0018     property int inPoint
0019     property int outPoint
0020     property int clipId
0021     property int modelStart
0022     property bool selected
0023     property var kfrModel
0024     property int scrollStart
0025     property alias kfrCanvas: keyframecanvas
0026     signal seek(int position)
0027 
0028     onKfrCountChanged: {
0029         keyframecanvas.requestPaint()
0030     }
0031 
0032     onInPointChanged: {
0033         keyframecanvas.requestPaint()
0034     }
0035 
0036     onOutPointChanged: {
0037         keyframecanvas.requestPaint()
0038     }
0039 
0040     function resetSelection() {
0041         kfrModel.setActiveKeyframe(-1)
0042         keyframeContainer.activeIndex = -1
0043         keyframeContainer.focus = false
0044         kfrModel.setSelectedKeyframe(-1, false)
0045     }
0046 
0047     Keys.onShortcutOverride: {
0048         if (event.key === Qt.Key_Left) {
0049             if (event.modifiers & Qt.AltModifier) {
0050                 kfrModel.setActiveKeyframe(Math.max(0, --activeIndex))
0051                 seek(keyframes.itemAt(kfrModel.activeKeyframe).value + keyframeContainer.modelStart - keyframeContainer.inPoint)
0052                 event.accepted = true
0053             } else {
0054                 var oldFrame = keyframes.itemAt(kfrModel.activeKeyframe).value
0055                 var newPos = Math.max(oldFrame - 1 - keyframeContainer.inPoint, 0)
0056                 if (newPos != oldFrame) {
0057                     timeline.updateEffectKeyframe(clipId, oldFrame, newPos)
0058                     event.accepted = true
0059                 }
0060             }
0061         }
0062         else if (event.key === Qt.Key_Right) {
0063             if (event.modifiers & Qt.AltModifier) {
0064                 kfrModel.setActiveKeyframe(Math.min(keyframes.count - 1, ++activeIndex))
0065                 seek(keyframes.itemAt(kfrModel.activeKeyframe()).value + keyframeContainer.modelStart - keyframeContainer.inPoint)
0066             } else {
0067                 var oldFrame = keyframes.itemAt(kfrModel.activeKeyframe()).value
0068                 var newPos = Math.min(oldFrame + 1 - keyframeContainer.inPoint, keyframeContainer.outPoint - keyframeContainer.inPoint)
0069                 if (newPos != oldFrame) {
0070                     timeline.updateEffectKeyframe(clipId, oldFrame, newPos)
0071                 }
0072             }
0073             event.accepted = true
0074         }
0075         else if (event.key === Qt.Key_Return || event.key === Qt.Key_Escape) {
0076             keyframeContainer.focus = false
0077             event.accepted = true
0078         }
0079         if ((event.key === Qt.Key_Plus) && !(event.modifiers & Qt.ControlModifier)) {
0080             var newVal = Math.min(keyframes.itemAt(activeIndex).value / parent.height + .05, 1)
0081             kfrModel.updateKeyframe(kfrModel.activeKeyframe(), newVal)
0082             event.accepted = true
0083         }
0084         else if ((event.key === Qt.Key_Minus) && !(event.modifiers & Qt.ControlModifier)) {
0085             var newVal = Math.max(keyframes.itemAt(activeIndex).value / parent.height - .05, 0)
0086             kfrModel.updateKeyframe(kfrModel.activeKeyframe(), newVal)
0087             event.accepted = true
0088         } else {
0089             event.accepted = false
0090         }
0091     }
0092     Item {
0093         // Keyframes container
0094         anchors.fill: parent
0095         z: 5
0096         visible: keyframeContainer.selected && keyframeContainer.width > root.baseUnit * 3 && (kfrCount < (keyframeContainer.width / root.baseUnit)) && kfrCount > 1
0097         Repeater {
0098             id: keyframes
0099             model: kfrModel
0100             Rectangle {
0101                 id: keyframe
0102                 visible: root.activeTool === ProjectTool.SelectTool
0103                 property int frame : model.frame
0104                 property int frameType : model.type
0105                 property string realValue: model.value
0106                 x: (model.frame - keyframeContainer.inPoint) * timeScale
0107                 height: parent.height
0108                 property int value: parent.height * model.normalizedValue
0109                 property int tmpVal : keyframeVal.y + root.baseUnit / 2
0110                 property int tmpPos : x + keyframeVal.x + root.baseUnit / 2
0111                 property int dragPos : -1
0112                 anchors.bottom: parent.bottom
0113                 onFrameTypeChanged: {
0114                     keyframecanvas.requestPaint()
0115                 }
0116                 onValueChanged: {
0117                     keyframecanvas.requestPaint()
0118                 }
0119                 onRealValueChanged: {
0120                     kf1MouseArea.movingVal = kfrModel.realValue(model.normalizedValue)
0121                 }
0122                 width: Math.max(1, timeScale)
0123                 color: kfMouseArea.containsMouse ? 'darkred' : 'transparent'
0124                 MouseArea {
0125                     id: kfMouseArea
0126                     anchors.fill: parent
0127                     anchors.leftMargin: - root.baseUnit/3
0128                     anchors.rightMargin: - root.baseUnit/3
0129                     hoverEnabled: true
0130                     cursorShape: Qt.SizeHorCursor
0131                     enabled: parent.x > root.baseUnit / 2 && parent.x < keyframeContainer.width - root.baseUnit / 2
0132                     drag.target: parent
0133                     drag.smoothed: false
0134                     drag.axis: Drag.XAxis
0135                     onReleased: {
0136                         root.autoScrolling = timeline.autoScroll
0137                         dragPos = -1
0138                         var newPos = Math.round(parent.x / timeScale) + keyframeContainer.inPoint
0139                         if (frame != keyframeContainer.inPoint && newPos != frame) {
0140                             if (mouse.modifiers & Qt.ShiftModifier) {
0141                                 // offset all subsequent keyframes
0142                                 // TODO: rewrite using timeline to ensure all kf parameters are updated
0143                                 timeline.offsetKeyframes(clipId, frame, newPos)
0144                             } else {
0145                                 timeline.updateEffectKeyframe(clipId, frame, newPos)
0146                             }
0147                         }
0148                     }
0149                     onPositionChanged: {
0150                         if (mouse.buttons === Qt.LeftButton) {
0151                             if (frame == keyframeContainer.inPoint) {
0152                                 parent.x = keyframeContainer.inPoint * timeScale
0153                                 return
0154                             }
0155                             var newPos = Math.min(Math.round(parent.x / timeScale), Math.round(keyframeContainer.width / timeScale) - 1)
0156                             if (newPos < 1) {
0157                                 newPos = 1
0158                             }
0159                             if (newPos != dragPos && (newPos == 0 || !timeline.hasKeyframeAt(clipId, frame + newPos))) {
0160                                 dragPos = newPos
0161                                 parent.x = newPos * timeScale
0162                                 keyframecanvas.requestPaint()
0163                             } else {
0164                                 parent.x = dragPos * timeScale
0165                             }
0166                         }
0167                     }
0168                     onEntered: {
0169                         timeline.showKeyBinding(i18n("<b>Drag</b> to move selected keyframes position. <b>Shift drag</b> to move all keyframes after this one."))
0170                     }
0171                     onExited: {
0172                         timeline.showKeyBinding()
0173                     }
0174                 }
0175                 Rectangle {
0176                     id: keyframeVal
0177                     x: - root.baseUnit / 2
0178                     y: keyframeContainer.height - keyframe.value - root.baseUnit / 2
0179                     width: root.baseUnit
0180                     height: width
0181                     radius: width / 2
0182                     color: model.active ? 'red' : model.selected ? 'orange' : kf1MouseArea.containsMouse || kf1MouseArea.pressed ? root.textColor : root.videoColor
0183                     border.color: kf1MouseArea.containsMouse || kf1MouseArea.pressed ? activePalette.highlight : root.textColor
0184 
0185                     MouseArea {
0186                         id: kf1MouseArea
0187                         anchors.fill: parent
0188                         hoverEnabled: true
0189                         cursorShape: shiftPressed ? Qt.SizeVerCursor : Qt.PointingHandCursor
0190                         drag.target: parent
0191                         drag.smoothed: false
0192                         drag.threshold: 1
0193                         property string movingVal: kfrModel.realValue(model.normalizedValue)
0194                         property double newVal: NaN
0195                         property bool shiftPressed: false
0196                         onPressed: {
0197                             drag.axis = model.moveOnly ? Drag.XAxis : (mouse.modifiers & Qt.ShiftModifier) ? Drag.YAxis : Drag.XAndYAxis
0198                         }
0199                         onClicked: {
0200                             keyframeContainer.focus = true
0201                             if (mouse.modifiers & Qt.ControlModifier && model.selected) {
0202                                 kfrModel.setActiveKeyframe(-1)
0203                                 keyframeContainer.activeIndex = -1
0204                                 kfrModel.setSelectedKeyframe(index, true)
0205                             } else {
0206                                 kfrModel.setActiveKeyframe(index)
0207                                 keyframeContainer.activeIndex = index
0208                                 kfrModel.setSelectedKeyframe(index, mouse.modifiers & Qt.ControlModifier)
0209                             }
0210                             var ix = kfrModel.activeKeyframe()
0211                             if (ix > -1) {
0212                                 seek(keyframes.itemAt(ix).frame + keyframeContainer.modelStart - keyframeContainer.inPoint)
0213                             }
0214                         }
0215                         onReleased: {
0216                             if (isNaN(newVal)) {
0217                                 return
0218                             }
0219                             root.autoScrolling = timeline.autoScroll
0220                             var newPos = frame == keyframeContainer.inPoint ? keyframeContainer.inPoint : Math.round((keyframe.x + parent.x + root.baseUnit / 2) / timeScale) + keyframeContainer.inPoint
0221                             if (newPos === frame && keyframe.value == keyframe.height - parent.y - root.baseUnit / 2) {
0222                                 var pos = keyframeContainer.modelStart + frame - keyframeContainer.inPoint
0223                                 if (proxy.position != pos) {
0224                                     seek(pos)
0225                                 }
0226                                 return
0227                             }
0228                             if (newVal > 1.5 || newVal < -0.5) {
0229                                 if (frame != keyframeContainer.inPoint) {
0230                                     keyframeContainer.resetSelection()
0231                                     timeline.removeEffectKeyframe(clipId, frame);
0232                                 } else {
0233                                     if (newVal < 0) {
0234                                         newVal = 0;
0235                                     } else if (newVal > 1) {
0236                                         newVal = 1;
0237                                     }
0238                                     timeline.updateEffectKeyframe(clipId, frame, frame, newVal)
0239                                 }
0240                             } else {
0241                                 if (newVal < 0) {
0242                                     newVal = 0;
0243                                 } else if (newVal > 1) {
0244                                     newVal = 1;
0245                                 }
0246                                 if (model.moveOnly) {
0247                                     timeline.updateEffectKeyframe(clipId, frame, newPos)
0248                                 } else {
0249                                     timeline.updateEffectKeyframe(clipId, frame, frame == keyframeContainer.inPoint ? frame : newPos, newVal)
0250                                 }
0251                             }
0252                         }
0253                         onPositionChanged: {
0254                             shiftPressed = (mouse.modifiers & Qt.ShiftModifier)
0255                             if (mouse.buttons === Qt.LeftButton) {
0256                                 if (frame == keyframeContainer.inPoint) {
0257                                     parent.x = - root.baseUnit / 2
0258                                 } else {
0259                                     var newPos = Math.min(Math.round((parent.x + root.baseUnit / 2) / timeScale), Math.round(keyframeContainer.width / timeScale) - frame + keyframeContainer.inPoint - 1)
0260                                     if (frame + newPos <= keyframeContainer.inPoint) {
0261                                         newPos = keyframeContainer.inPoint + 1 - frame
0262                                     }
0263                                     if (newPos != dragPos && (newPos == 0 || !timeline.hasKeyframeAt(clipId, frame + newPos))) {
0264                                         dragPos = newPos
0265                                         parent.x = newPos * timeScale - root.baseUnit / 2
0266                                         keyframecanvas.requestPaint()
0267                                     } else {
0268                                         parent.x = dragPos * timeScale - root.baseUnit / 2
0269                                     }
0270                                 }
0271                                 keyframecanvas.requestPaint()
0272                                 newVal = (keyframeContainer.height - (parent.y + mouse.y)) / keyframeContainer.height
0273                                 movingVal = kfrModel.realValue(Math.min(Math.max(newVal, 0), 1))
0274                             }
0275                         }
0276                         onDoubleClicked: {
0277                             keyframeContainer.resetSelection()
0278                             timeline.removeEffectKeyframe(clipId, frame);
0279                         }
0280                         onEntered: {
0281                             timeline.showKeyBinding(i18n("<b>Shift drag</b> to change value of selected keyframes, <b>Ctrl click</b> for multiple keyframe selection."))
0282                         }
0283                         onExited: {
0284                             timeline.showKeyBinding()
0285                         }
0286                         ToolTip.visible: (containsMouse || pressed) && movingVal != ""
0287                         ToolTip.text: movingVal
0288                     }
0289                 }
0290             }
0291         }
0292     }
0293     Canvas {
0294         id: keyframecanvas
0295         contextType: "2d"
0296         renderStrategy: Canvas.Threaded
0297         property int offset: scrollStart < 0 || parent.width <= scrollView.width ? 0 : scrollStart
0298         anchors.left: parent.left
0299         anchors.leftMargin: offset
0300         width: kfrCount > 0 ? Math.min(parent.width, scrollView.width) : 0
0301         height: kfrCount > 0 ? parent.height : 0
0302         opacity: keyframeContainer.selected ? 1 : 0.5
0303         Component {
0304             id: comp
0305             PathCurve { }
0306         }
0307         Component {
0308             id: compline
0309             PathLine { }
0310         }
0311         property var paths : []
0312         Path {
0313             id: myPath
0314             startX: 0
0315             startY: parent.height
0316         }
0317 
0318         onPaint: {
0319             if (kfrCount < 1) {
0320                 return
0321             }
0322             var ctx = getContext("2d");
0323             ctx.beginPath()
0324             ctx.fillStyle = Qt.rgba(0,0,0.8, 0.5);
0325             paths = []
0326             var xpos
0327             var ypos
0328             for(var i = 0; i < keyframes.count; i++)
0329             {
0330                 if (i + 1 < keyframes.count) {
0331                     if (keyframes.itemAt(i + 1).tmpPos < offset) {
0332                         continue;
0333                     }
0334                 }
0335                 xpos = keyframes.itemAt(i).tmpPos - offset
0336                 var type = i > 0 ? keyframes.itemAt(i-1).frameType : keyframes.itemAt(i).frameType
0337                 switch (type) {
0338                     case 0:
0339                         // discrete
0340                         paths.push(compline.createObject(keyframecanvas, {"x": xpos, "y": ypos} ))
0341                         break;
0342                     case 2:
0343                         // curve
0344                         ypos = keyframes.itemAt(i).tmpVal
0345                         paths.push(comp.createObject(keyframecanvas, {"x": xpos, "y": ypos} ))
0346                         break;
0347                     default:
0348                         // linear of others
0349                         ypos = keyframes.itemAt(i).tmpVal
0350                         paths.push(compline.createObject(keyframecanvas, {"x": xpos, "y": ypos} ))
0351                         break;
0352                 }
0353                 if (xpos > scrollView.width) {
0354                     break;
0355                 }
0356             }
0357             paths.push(compline.createObject(keyframecanvas, {"x": keyframecanvas.width, "y": ypos} ))
0358             paths.push(compline.createObject(keyframecanvas, {"x": keyframecanvas.width, "y": keyframecanvas.height} ))
0359             myPath.pathElements = paths
0360             ctx.clearRect(0,0, width, height);
0361             ctx.path = myPath;
0362             ctx.closePath()
0363             ctx.fill()
0364         }
0365     }
0366 }