diff --git a/src/timeline2/view/qml/Composition.qml b/src/timeline2/view/qml/Composition.qml index d5962cd47..56329c888 100644 --- a/src/timeline2/view/qml/Composition.qml +++ b/src/timeline2/view/qml/Composition.qml @@ -1,379 +1,379 @@ /*************************************************************************** * Copyright (C) 2017 by Jean-Baptiste Mardelle * * This file is part of Kdenlive. See www.kdenlive.org. * * Based on work by Dan Dennedy (Shotcut) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ import QtQuick 2.11 import QtQuick.Controls 2.4 import QtQml.Models 2.11 import QtQuick.Window 2.2 import 'Timeline.js' as Logic Item { id: compositionRoot property real timeScale: 1 property string clipName: '' property string clipResource: '' property string mltService: '' property int modelStart property int displayHeight: 0 property var parentTrack: trackRoot property int inPoint: 0 property int outPoint: 0 property int clipDuration: 0 property bool isAudio: false property bool showKeyframes: false property var itemType: 0 property bool isGrabbed: false property var keyframeModel property bool grouped: false property int binId: 0 property int scrollX: 0 property int trackHeight property int trackIndex //Index in track repeater property int trackId: -42 //Id in the model property int aTrack: -1 property int clipId //Id of the clip in the model property int originalTrackId: trackId property bool isComposition: true property int originalX: x property int originalDuration: clipDuration property int lastValidDuration: clipDuration property int draggedX: x property bool selected: false property double speed: 1.0 property color color: displayRect.color property color borderColor: 'black' property bool hideCompoViews property int scrollStart: scrollView.contentX - modelStart * timeline.scaleFactor property int mouseXPos: mouseArea.mouseX signal moved(var clip) signal dragged(var clip, var mouse) signal dropped(var clip) signal draggedToTrack(var clip, int pos, int xpos) signal trimmingIn(var clip, real newDuration, var mouse) signal trimmedIn(var clip) signal trimmingOut(var clip, real newDuration, var mouse) signal trimmedOut(var clip) onScrollStartChanged: { compositionRoot.hideCompoViews = compositionRoot.scrollStart > width || compositionRoot.scrollStart + scrollView.contentItem.width < 0 } onKeyframeModelChanged: { if (effectRow.keyframecanvas) { effectRow.keyframecanvas.requestPaint() } } onModelStartChanged: { x = modelStart * timeScale; } onIsGrabbedChanged: { if (compositionRoot.isGrabbed) { grabItem() } } function grabItem() { compositionRoot.forceActiveFocus() mouseArea.focus = true } onTrackIdChanged: { compositionRoot.parentTrack = Logic.getTrackById(trackId) compositionRoot.y = compositionRoot.originalTrackId == -1 || trackId == originalTrackId ? 0 : parentTrack.y - Logic.getTrackById(compositionRoot.originalTrackId).y; } onClipDurationChanged: { width = clipDuration * timeScale; } onTimeScaleChanged: { x = modelStart * timeScale; width = clipDuration * timeScale; labelRect.x = scrollX > modelStart * timeScale ? scrollX - modelStart * timeScale : 0 } onScrollXChanged: { labelRect.x = scrollX > modelStart * timeScale ? scrollX - modelStart * timeScale : 0 } /* function reparent(track) { parent = track isAudio = track.isAudio parentTrack = track displayHeight = track.height / 2 compositionRoot.trackId = parentTrack.trackId } */ function updateDrag() { console.log('XXXXXXXXXXXXXXX\n\nXXXXXXXXXXXXX \nUPDATING COMPO DRAG') var itemPos = mapToItem(tracksContainerArea, 0, displayRect.y, displayRect.width, displayRect.height) initDrag(compositionRoot, itemPos, compositionRoot.clipId, compositionRoot.modelStart, compositionRoot.trackId, true) } MouseArea { id: mouseArea anchors.fill: displayRect acceptedButtons: Qt.RightButton hoverEnabled: true Keys.onShortcutOverride: event.accepted = compositionRoot.isGrabbed && (event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Up || event.key === Qt.Key_Down || event.key === Qt.Key_Escape) Keys.onLeftPressed: { var offset = event.modifiers === Qt.ShiftModifier ? timeline.fps() : 1 controller.requestCompositionMove(compositionRoot.clipId, compositionRoot.originalTrackId, compositionRoot.modelStart - offset, true, true) } Keys.onRightPressed: { var offset = event.modifiers === Qt.ShiftModifier ? timeline.fps() : 1 controller.requestCompositionMove(compositionRoot.clipId, compositionRoot.originalTrackId, compositionRoot.modelStart + offset, true, true) } Keys.onUpPressed: { controller.requestCompositionMove(compositionRoot.clipId, controller.getNextTrackId(compositionRoot.originalTrackId), compositionRoot.modelStart, true, true) } Keys.onDownPressed: { controller.requestCompositionMove(compositionRoot.clipId, controller.getPreviousTrackId(compositionRoot.originalTrackId), compositionRoot.modelStart, true, true) } Keys.onEscapePressed: { timeline.grabCurrent() } cursorShape: (trimInMouseArea.drag.active || trimOutMouseArea.drag.active)? Qt.SizeHorCursor : dragProxyArea.cursorShape onPressed: { root.autoScrolling = false compositionRoot.forceActiveFocus(); root.mainItemId = compositionRoot.clipId if (mouse.button == Qt.RightButton) { if (timeline.selection.indexOf(compositionRoot.clipId) == -1) { controller.requestAddToSelection(compositionRoot.clipId, true) } root.showCompositionMenu() } } onReleased: { root.autoScrolling = timeline.autoScroll } onEntered: { var itemPos = mapToItem(tracksContainerArea, 0, 0, width, height) initDrag(compositionRoot, itemPos, compositionRoot.clipId, compositionRoot.modelStart, compositionRoot.trackId, true) } onExited: { endDrag() } onDoubleClicked: { if (mouse.modifiers & Qt.ShiftModifier) { if (keyframeModel && showKeyframes) { // Add new keyframe var xPos = Math.round(mouse.x / timeline.scaleFactor) var yPos = (compositionRoot.height - mouse.y) / compositionRoot.height keyframeModel.addKeyframe(xPos + compositionRoot.inPoint, yPos) } else { proxy.position = compositionRoot.x / timeline.scaleFactor } } else { timeline.editItemDuration(clipId) } } onPositionChanged: { if (parentTrack) { var mapped = parentTrack.mapFromItem(compositionRoot, mouse.x, mouse.y).x root.mousePosChanged(Math.round(mapped / timeline.scaleFactor)) } } onWheel: zoomByWheel(wheel) } Rectangle { id: displayRect anchors.top: compositionRoot.top anchors.right: compositionRoot.right anchors.left: compositionRoot.left - anchors.topMargin: displayHeight * 1.2 - height: parentTrack.height - displayHeight * 1.2 + Logic.getTrackHeightByPos(Logic.getTrackIndexFromId(parentTrack.trackInternalId) + 1) / 3 + anchors.topMargin: displayHeight + height: parentTrack.height - displayHeight + (compositionRoot.selected ? Math.min(Logic.getTrackHeightByPos(Logic.getTrackIndexFromId(parentTrack.trackInternalId) + 1) / 3, root.baseUnit) : 0) color: selected ? 'mediumpurple' : Qt.darker('mediumpurple') border.color: selected ? root.selectionColor : grouped ? root.groupColor : borderColor border.width: isGrabbed ? 8 : 1.5 opacity: dragProxyArea.drag.active && dragProxy.draggedItem == clipId ? 0.5 : 1.0 Item { // clipping container id: container anchors.fill: displayRect anchors.margins:1.5 clip: true Rectangle { // text background id: labelRect color: compositionRoot.aTrack > -1 ? 'yellow' : 'lightgray' anchors.top: container.top width: label.width + 2 height: label.height Text { id: label text: clipName + (compositionRoot.aTrack > -1 ? ' > ' + timeline.getTrackNameFromMltIndex(compositionRoot.aTrack) : '') font: miniFont anchors { top: labelRect.top left: labelRect.left topMargin: 1 leftMargin: 1 } color: 'black' } } KeyframeView { id: effectRow visible: compositionRoot.showKeyframes && compositionRoot.keyframeModel selected: compositionRoot.selected inPoint: 0 outPoint: compositionRoot.clipDuration masterObject: compositionRoot kfrModel: compositionRoot.hideCompoViews ? 0 : compositionRoot.keyframeModel } ToolTip { visible: mouseArea.containsMouse && !trimInMouseArea.containsMouse && !trimOutMouseArea.containsMouse && !trimInMouseArea.drag.active && !trimOutMouseArea.drag.active delay: 1000 timeout: 5000 background: Rectangle { color: activePalette.alternateBase border.color: activePalette.light } contentItem: Label { color: activePalette.text font: miniFont text: '%1\n%4: %5'.arg(label.text) .arg(i18n("Duration")) .arg(timeline.simplifiedTC(compositionRoot.clipDuration)) } } } /*Drag.active: mouseArea.drag.active Drag.proposedAction: Qt.MoveAction*/ states: [ State { name: 'normal' when: !compositionRoot.selected PropertyChanges { target: compositionRoot z: 0 } }, State { name: 'selected' when: compositionRoot.selected PropertyChanges { target: compositionRoot z: 1 color: 'mediumpurple' } } ] Rectangle { id: trimIn anchors.left: displayRect.left anchors.leftMargin: 0 height: displayRect.height width: root.baseUnit / 3 color: isAudio? 'green' : 'lawngreen' opacity: 0 Drag.active: trimInMouseArea.drag.active Drag.proposedAction: Qt.MoveAction enabled: !compositionRoot.grouped visible: trimInMouseArea.pressed || (root.activeTool === 0 && !dragProxyArea.drag.active && compositionRoot.width > 4 * width) MouseArea { id: trimInMouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.SizeHorCursor drag.target: parent drag.axis: Drag.XAxis drag.smoothed: false onPressed: { root.autoScrolling = false compositionRoot.originalX = compositionRoot.x compositionRoot.originalDuration = clipDuration parent.anchors.left = undefined } onReleased: { root.autoScrolling = timeline.autoScroll parent.anchors.left = displayRect.left compositionRoot.trimmedIn(compositionRoot) parent.opacity = 0 } onPositionChanged: { if (mouse.buttons === Qt.LeftButton) { var delta = Math.round((trimIn.x) / timeScale) if (delta < -modelStart) { delta = -modelStart } if (delta !== 0) { var newDuration = compositionRoot.clipDuration - delta compositionRoot.trimmingIn(compositionRoot, newDuration, mouse) } } } onEntered: parent.opacity = 0.5 onExited: parent.opacity = 0 } } Rectangle { id: trimOut anchors.right: displayRect.right anchors.rightMargin: 0 height: displayRect.height width: root.baseUnit / 3 color: 'red' opacity: 0 Drag.active: trimOutMouseArea.drag.active Drag.proposedAction: Qt.MoveAction enabled: !compositionRoot.grouped visible: trimOutMouseArea.pressed || (root.activeTool === 0 && !dragProxyArea.drag.active && compositionRoot.width > 4 * width) MouseArea { id: trimOutMouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.SizeHorCursor drag.target: parent drag.axis: Drag.XAxis drag.smoothed: false onPressed: { root.autoScrolling = false compositionRoot.originalDuration = clipDuration parent.anchors.right = undefined } onReleased: { root.autoScrolling = timeline.autoScroll parent.anchors.right = displayRect.right compositionRoot.trimmedOut(compositionRoot) } onPositionChanged: { if (mouse.buttons === Qt.LeftButton) { var newDuration = Math.round((parent.x + parent.width) / timeScale) compositionRoot.trimmingOut(compositionRoot, newDuration, mouse) } } onEntered: parent.opacity = 0.5 onExited: parent.opacity = 0 } } } } diff --git a/src/timeline2/view/qml/Track.qml b/src/timeline2/view/qml/Track.qml index 44d1f7673..3e10a9d9e 100644 --- a/src/timeline2/view/qml/Track.qml +++ b/src/timeline2/view/qml/Track.qml @@ -1,433 +1,433 @@ /* * Copyright (c) 2013-2016 Meltytech, LLC * Author: Dan Dennedy * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.11 import QtQml.Models 2.11 import com.enums 1.0 Item{ id: trackRoot property alias trackModel: trackModel.model property alias rootIndex : trackModel.rootIndex property bool isAudio property real timeScale: 1.0 property bool isCurrentTrack: false property bool isLocked: false property int trackInternalId : -42 property int trackThumbsFormat property int itemType: 0 opacity: model.disabled ? 0.4 : 1 function clipAt(index) { return repeater.itemAt(index) } function isClip(type) { return type != ProducerType.Composition && type != ProducerType.Track; } width: clipRow.width DelegateModel { id: trackModel delegate: Item { property var itemModel : model z: model.clipType == ProducerType.Composition ? 5 : 0 Loader { id: loader Binding { target: loader.item property: "timeScale" value: trackRoot.timeScale when: loader.status == Loader.Ready && loader.item } Binding { target: loader.item property: "fakeTid" value: model.fakeTrackId when: loader.status == Loader.Ready && loader.item && isClip(model.clipType) } Binding { target: loader.item property: "fakePosition" value: model.fakePosition when: loader.status == Loader.Ready && loader.item && isClip(model.clipType) } Binding { target: loader.item property: "selected" value: model.selected when: loader.status == Loader.Ready && model.clipType != ProducerType.Track } Binding { target: loader.item property: "mltService" value: model.mlt_service when: loader.status == Loader.Ready && loader.item } Binding { target: loader.item property: "modelStart" value: model.start when: loader.status == Loader.Ready && loader.item } Binding { target: loader.item property: "scrollX" value: scrollView.contentX when: loader.status == Loader.Ready && loader.item } Binding { target: loader.item property: "fadeIn" value: model.fadeIn when: loader.status == Loader.Ready && isClip(model.clipType) } Binding { target: loader.item property: "positionOffset" value: model.positionOffset when: loader.status == Loader.Ready && isClip(model.clipType) } Binding { target: loader.item property: "effectNames" value: model.effectNames when: loader.status == Loader.Ready && isClip(model.clipType) } Binding { target: loader.item property: "clipStatus" value: model.clipStatus when: loader.status == Loader.Ready && isClip(model.clipType) } Binding { target: loader.item property: "fadeOut" value: model.fadeOut when: loader.status == Loader.Ready && isClip(model.clipType) } Binding { target: loader.item property: "showKeyframes" value: model.showKeyframes when: loader.status == Loader.Ready && loader.item } Binding { target: loader.item property: "isGrabbed" value: model.isGrabbed when: loader.status == Loader.Ready && loader.item } Binding { target: loader.item property: "keyframeModel" value: model.keyframeModel when: loader.status == Loader.Ready && loader.item } Binding { target: loader.item property: "aTrack" value: model.a_track when: loader.status == Loader.Ready && model.clipType == ProducerType.Composition } Binding { target: loader.item property: "trackHeight" value: root.trackHeight when: loader.status == Loader.Ready && model.clipType == ProducerType.Composition } Binding { target: loader.item property: "clipDuration" value: model.duration when: loader.status == Loader.Ready && loader.item } Binding { target: loader.item property: "inPoint" value: model.in when: loader.status == Loader.Ready && loader.item } Binding { target: loader.item property: "outPoint" value: model.out when: loader.status == Loader.Ready && loader.item } Binding { target: loader.item property: "grouped" value: model.grouped when: loader.status == Loader.Ready && loader.item } Binding { target: loader.item property: "clipName" value: model.name when: loader.status == Loader.Ready && loader.item } Binding { target: loader.item property: "clipResource" value: model.resource when: loader.status == Loader.Ready && isClip(model.clipType) } Binding { target: loader.item property: "isProxy" value: model.isProxy when: loader.status == Loader.Ready && isClip(model.clipType) } Binding { target: loader.item property: "maxDuration" value: model.maxDuration when: loader.status == Loader.Ready && isClip(model.clipType) } Binding { target: loader.item property: "forceReloadThumb" value: model.reloadThumb when: loader.status == Loader.Ready && isClip(model.clipType) } Binding { target: loader.item property: "binId" value: model.binId when: loader.status == Loader.Ready && isClip(model.clipType) } sourceComponent: { if (isClip(model.clipType)) { return clipDelegate } else if (model.clipType == ProducerType.Composition) { return compositionDelegate } else { // Track return undefined } } onLoaded: { item.clipId= model.item item.parentTrack = trackRoot if (isClip(model.clipType)) { console.log('loaded clip: ', model.start, ', ID: ', model.item, ', index: ', trackRoot.DelegateModel.itemsIndex,', TYPE:', model.clipType) item.isAudio= model.audio item.markers= model.markers item.hasAudio = model.hasAudio item.canBeAudio = model.canBeAudio item.canBeVideo = model.canBeVideo item.itemType = model.clipType item.audioChannels = model.audioChannels item.audioStream = model.audioStream item.aStreamIndex = model.audioStreamIndex console.log('loaded clpi with Astream: ', model.audioStream) // Speed change triggers a new clip insert so no binding necessary item.speed = model.speed } else if (model.clipType == ProducerType.Composition) { console.log('loaded composition: ', model.start, ', ID: ', model.item, ', index: ', trackRoot.DelegateModel.itemsIndex) //item.aTrack = model.a_track } else { console.log('loaded unwanted element: ', model.item, ', index: ', trackRoot.DelegateModel.itemsIndex) } item.trackId = model.trackId //item.selected= trackRoot.selection.indexOf(item.clipId) !== -1 //console.log(width, height); } } } } Item { id: clipRow height: trackRoot.height Repeater { id: repeater; model: trackModel } } Component { id: clipDelegate Clip { height: trackRoot.height onInitGroupTrim: { // We are resizing a group, remember coordinates of all elements clip.groupTrimData = controller.getGroupData(clip.clipId) } onTrimmingIn: { if (controlTrim) { newDuration = controller.requestItemSpeedChange(clip.clipId, newDuration, false, root.snapping) if (!speedController.visible) { // Store original speed speedController.originalSpeed = clip.speed } clip.x += clip.width - (newDuration * trackRoot.timeScale) clip.width = newDuration * root.timeScale speedController.x = clip.x + clip.border.width speedController.width = clip.width - 2 * clip.border.width speedController.lastValidDuration = newDuration clip.speed = clip.originalDuration * speedController.originalSpeed / newDuration speedController.visible = true return } var new_duration = controller.requestItemResize(clip.clipId, newDuration, false, false, root.snapping, shiftTrim) if (new_duration > 0) { clip.lastValidDuration = new_duration clip.originalX = clip.draggedX // Show amount trimmed as a time in a "bubble" help. var delta = new_duration - clip.originalDuration var s = timeline.simplifiedTC(Math.abs(delta)) s = '%1%2\n%3:%4'.arg((delta <= 0)? '+' : '-') .arg(s) .arg(i18n("In")) .arg(timeline.simplifiedTC(clip.inPoint)) bubbleHelp.show(clip.x - 20, trackRoot.y + trackRoot.height, s) } } onTrimmedIn: { bubbleHelp.hide() if (shiftTrim || clip.groupTrimData == undefined || controlTrim) { // We only resize one element controller.requestItemResize(clip.clipId, clip.originalDuration, false, false, 0, shiftTrim) if (controlTrim) { // Update speed speedController.visible = false controller.requestClipResizeAndTimeWarp(clip.clipId, speedController.lastValidDuration, false, root.snapping, shiftTrim, clip.originalDuration * speedController.originalSpeed / speedController.lastValidDuration) speedController.originalSpeed = 1 } else { controller.requestItemResize(clip.clipId, clip.lastValidDuration, false, true, 0, shiftTrim) } } else { var updatedGroupData = controller.getGroupData(clip.clipId) controller.processGroupResize(clip.groupTrimData, updatedGroupData, false) } clip.groupTrimData = undefined } onTrimmingOut: { if (controlTrim) { if (!speedController.visible) { // Store original speed speedController.originalSpeed = clip.speed } speedController.x = clip.x + clip.border.width newDuration = controller.requestItemSpeedChange(clip.clipId, newDuration, true, root.snapping) clip.width = newDuration * trackRoot.timeScale speedController.width = clip.width - 2 * clip.border.width speedController.lastValidDuration = newDuration clip.speed = clip.originalDuration * speedController.originalSpeed / newDuration speedController.visible = true return } var new_duration = controller.requestItemResize(clip.clipId, newDuration, true, false, root.snapping, shiftTrim) if (new_duration > 0) { clip.lastValidDuration = new_duration // Show amount trimmed as a time in a "bubble" help. var delta = clip.originalDuration - new_duration var s = timeline.simplifiedTC(Math.abs(delta)) s = '%1%2\n%3:%4'.arg((delta <= 0)? '+' : '-') .arg(s) .arg(i18n("Duration")) .arg(timeline.simplifiedTC(new_duration)) bubbleHelp.show(clip.x + clip.width - 20, trackRoot.y + trackRoot.height, s) } } onTrimmedOut: { bubbleHelp.hide() if (shiftTrim || clip.groupTrimData == undefined || controlTrim) { controller.requestItemResize(clip.clipId, clip.originalDuration, true, false, 0, shiftTrim) if (controlTrim) { speedController.visible = false // Update speed controller.requestClipResizeAndTimeWarp(clip.clipId, speedController.lastValidDuration, true, root.snapping, shiftTrim, clip.originalDuration * speedController.originalSpeed / speedController.lastValidDuration) speedController.originalSpeed = 1 } else { controller.requestItemResize(clip.clipId, clip.lastValidDuration, true, true, 0, shiftTrim) } } else { var updatedGroupData = controller.getGroupData(clip.clipId) controller.processGroupResize(clip.groupTrimData, updatedGroupData, true) } clip.groupTrimData = undefined } } } Component { id: compositionDelegate Composition { - displayHeight: trackRoot.height / 2 + displayHeight: Math.max(trackRoot.height / 2, trackRoot.height - (root.baseUnit * 2)) opacity: 0.8 selected: root.timelineSelection.indexOf(clipId) !== -1 onTrimmingIn: { var new_duration = controller.requestItemResize(clip.clipId, newDuration, false, false, root.snapping) if (new_duration > 0) { clip.lastValidDuration = newDuration clip.originalX = clip.draggedX // Show amount trimmed as a time in a "bubble" help. var delta = clip.originalDuration - new_duration var s = timeline.simplifiedTC(Math.abs(delta)) s = '%1%2 = %3'.arg((delta <= 0)? '+' : '-') .arg(s) .arg(timeline.simplifiedTC(new_duration)) bubbleHelp.show(clip.x + clip.width, trackRoot.y + trackRoot.height, s) } } onTrimmedIn: { bubbleHelp.hide() controller.requestItemResize(clip.clipId, clip.originalDuration, false, false, root.snapping) controller.requestItemResize(clip.clipId, clip.lastValidDuration, false, true, root.snapping) } onTrimmingOut: { var new_duration = controller.requestItemResize(clip.clipId, newDuration, true, false, root.snapping) if (new_duration > 0) { clip.lastValidDuration = newDuration // Show amount trimmed as a time in a "bubble" help. var delta = clip.originalDuration - new_duration var s = timeline.simplifiedTC(Math.abs(delta)) s = '%1%2 = %3'.arg((delta <= 0)? '+' : '-') .arg(s) .arg(timeline.simplifiedTC(new_duration)) bubbleHelp.show(clip.x + clip.width, trackRoot.y + trackRoot.height, s) } } onTrimmedOut: { bubbleHelp.hide() controller.requestItemResize(clip.clipId, clip.originalDuration, true, false, root.snapping) controller.requestItemResize(clip.clipId, clip.lastValidDuration, true, true, root.snapping) } } } Rectangle { id: speedController anchors.bottom: parent.bottom color: activePalette.highlight //'#cccc0000' visible: false height: root.baseUnit * 1.5 property int lastValidDuration: 0 property real originalSpeed: 1 Text { id: speedLabel text: i18n("Adjusting speed") font: miniFont anchors.fill: parent verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter color: activePalette.highlightedText } transitions: [ Transition { NumberAnimation { property: "opacity"; duration: 300} } ] } }