Commit a43a5377 authored by luyikei's avatar luyikei

Timeline: Implement transition

parent ec92497e
...@@ -217,6 +217,11 @@ Rectangle { ...@@ -217,6 +217,11 @@ Rectangle {
if ( uuid === "videoUuid" || uuid === "audioUuid" ) if ( uuid === "videoUuid" || uuid === "audioUuid" )
return; return;
thumbnailSource = "image://thumbnail/" + libraryUuid + "/0"; thumbnailSource = "image://thumbnail/" + libraryUuid + "/0";
for ( var i = 0; i < allTransitions.length; ++i ) {
if ( allTransitions[i].begin === position || allTransitions[i].end === position + length - 1 )
allTransitions[i].clips.push( uuid );
}
} }
Component.onDestruction: { Component.onDestruction: {
......
...@@ -9,11 +9,11 @@ Item { ...@@ -9,11 +9,11 @@ Item {
property int trackId property int trackId
property string type property string type
property ListModel clips property ListModel clips
property ListModel transitionModel
Rectangle { Item {
id: clipArea id: clipArea
x: trackInfo.width x: trackInfo.width
color: "#222222"
height: parent.height height: parent.height
width: track.width - initPosOfCursor width: track.width - initPosOfCursor
...@@ -34,23 +34,26 @@ Item { ...@@ -34,23 +34,26 @@ Item {
DropArea { DropArea {
id: dropArea id: dropArea
anchors.fill: parent anchors.fill: parent
keys: ["Clip", "vlmc/uuid"] keys: ["Clip", "Transition", "vlmc/uuid", "vlmc/transition_id"]
// Enum for drop mode // Enum for drop mode
readonly property var dropMode: { readonly property var dropMode: {
"New": 0, "New": 0,
"Move": 1, "Move": 1,
"TransitionNew": 2,
"TransitionMove": 3,
} }
property string currentUuid property string currentUuid
property var aClipInfo: null property var aClipInfo: null
property var vClipInfo: null property var vClipInfo: null
property var mode
property int lastPos: 0 property int lastPos: 0
property int deltaPos: 0 property int deltaPos: 0
onDropped: { onDropped: {
if ( drop.keys.indexOf( "vlmc/uuid" ) >= 0 ) { if ( mode === dropMode.New ) {
aClipInfo = findClipFromTrack( "Audio", trackId, "audioUuid" ); aClipInfo = findClipFromTrack( "Audio", trackId, "audioUuid" );
vClipInfo = findClipFromTrack( "Video", trackId, "videoUuid" ); vClipInfo = findClipFromTrack( "Video", trackId, "videoUuid" );
var pos = 0; var pos = 0;
...@@ -70,17 +73,38 @@ Item { ...@@ -70,17 +73,38 @@ Item {
adjustTracks( "Audio" ); adjustTracks( "Audio" );
adjustTracks( "Video" ); adjustTracks( "Video" );
} }
else if ( mode === dropMode.TransitionNew ) {
var transition = findTransitionItem( "transitionUuid" );
workflow.addTransitionBetweenTracks(
transition.identifier, transition.begin, transition.end,
transition.trackId - 1, transition.trackId, transition.type );
removeTransitionFromTrack( type, trackId, "transitionUuid" );
currentUuid = "";
}
} }
onExited: { onExited: {
if ( currentUuid !== "" ) { if ( currentUuid === "transitionUuid" ) {
removeTransitionFromTrack( type, trackId, "transitionUuid" );
}
else if ( currentUuid !== "" ) {
removeClipFromTrack( "Audio", trackId, "audioUuid" ); removeClipFromTrack( "Audio", trackId, "audioUuid" );
removeClipFromTrack( "Video", trackId, "videoUuid" ); removeClipFromTrack( "Video", trackId, "videoUuid" );
} }
} }
onEntered: { onEntered: {
if ( drag.keys.indexOf( "vlmc/uuid" ) >= 0 ) { if ( drag.keys.indexOf( "vlmc/uuid" ) >= 0 )
mode = dropMode.New;
else if ( drag.keys.indexOf( "vlmc/transition_id" ) >= 0 )
mode = dropMode.TransitionNew
else if ( drag.keys.indexOf( "Clip" ) >= 0 )
mode = dropMode.Move;
else if ( drag.keys.indexOf( "Transition" ) >= 0 )
mode = dropMode.TransitionMove;
if ( mode === dropMode.New ) {
clearSelectedClips(); clearSelectedClips();
if ( currentUuid === drag.getDataAsString( "vlmc/uuid" ) ) { if ( currentUuid === drag.getDataAsString( "vlmc/uuid" ) ) {
if ( aClipInfo ) if ( aClipInfo )
...@@ -109,13 +133,31 @@ Item { ...@@ -109,13 +133,31 @@ Item {
} }
lastPos = ptof( drag.x ); lastPos = ptof( drag.x );
} }
else { else if ( mode === dropMode.Move ) {
lastPos = ptof( drag.source.x ); lastPos = ptof( drag.source.x );
// HACK: Call onPositoinChanged forcely here. // HACK: Call onPositoinChanged forcely here.
// x will be rounded so it won't affect actual its position. // x will be rounded so it won't affect actual its position.
drag.source.x = drag.source.x + 0.000001; drag.source.x = drag.source.x + 0.000001;
drag.source.forcePosition(); // Restore the binding drag.source.forcePosition(); // Restore the binding
} }
else if ( mode === dropMode.TransitionNew ) {
if ( trackId > 0 ) {
var transition_id = drag.getDataAsString( "vlmc/transition_id" );
currentUuid = "transitionUuid";
addTransition( type, trackId, {
"identifier": transition_id,
"begin": ptof( drag.x ),
"end": ptof( drag.x + 100 ),
"uuid": currentUuid,
"identifier": transition_id }
);
}
lastPos = ptof( drag.x );
}
else if ( mode === dropMode.TransitionMove ) {
drag.source.trackId = trackId;
lastPos = ptof( drag.source.x );
}
} }
onPositionChanged: { onPositionChanged: {
...@@ -123,26 +165,19 @@ Item { ...@@ -123,26 +165,19 @@ Item {
if ( drag.source.resizing === true ) if ( drag.source.resizing === true )
return; return;
if ( drag.keys.indexOf( "vlmc/uuid" ) >= 0 ) if ( mode === dropMode.Move ) {
var dMode = dropMode.New;
else
dMode = dropMode.Move;
// Scroll to the drag source
if ( dMode === dropMode.Move )
drag.source.scrollToThis(); drag.source.scrollToThis();
if ( dMode === dropMode.Move ) {
deltaPos = ptof( drag.source.x ) - lastPos;
// Move to the top // Move to the top
drag.source.parent.parent.z = ++maxZ; drag.source.z = ++maxZ;
// Prepare newTrackId for all the selected clips // Prepare newTrackId for all the selected clips
var oldTrackId = drag.source.newTrackId; var oldTrackId = drag.source.newTrackId;
drag.source.newTrackId = trackId; drag.source.newTrackId = trackId;
sortSelectedClips( trackId - oldTrackId ,deltaPos ); deltaPos = ptof( drag.source.x ) - lastPos;
sortSelectedClips( trackId - oldTrackId, deltaPos );
var toMove = selectedClips.concat(); var toMove = selectedClips.concat();
// Check if there is any impossible move // Check if there is any impossible move
...@@ -179,89 +214,108 @@ Item { ...@@ -179,89 +214,108 @@ Item {
} }
} }
} }
else { else if ( mode === dropMode.New ){
toMove = selectedClips.concat(); toMove = selectedClips.concat();
deltaPos = ptof( drag.x ) - lastPos; deltaPos = ptof( drag.x ) - lastPos;
} }
else if ( mode === dropMode.TransitionNew )
{
deltaPos = ptof( drag.x ) - lastPos;
var tItem = findTransitionItem( "transitionUuid" );
tItem.begin += deltaPos;
tItem.end += deltaPos;
}
else if ( mode === dropMode.TransitionMove ) {
deltaPos = ptof( drag.source.x ) - lastPos;
drag.source.begin += deltaPos;
drag.source.end += deltaPos;
}
while ( toMove.length > 0 ) { if ( mode === dropMode.New || mode === dropMode.Move )
target = findClipItem( toMove[0] ); {
var oldPos = target.position; while ( toMove.length > 0 ) {
var newPos = findNewPosition( Math.max( oldPos + deltaPos, 0 ), target, drag.source, isMagneticMode ); target = findClipItem( toMove[0] );
deltaPos = newPos - oldPos; var oldPos = target.position;
var newPos = findNewPosition( Math.max( oldPos + deltaPos, 0 ), target, drag.source, isMagneticMode );
// Let's find newX of the linked clip deltaPos = newPos - oldPos;
for ( i = 0; i < target.linkedClips.length; ++i )
{ // Let's find newX of the linked clip
var linkedClipItem = findClipItem( target.linkedClips[i] ); for ( i = 0; i < target.linkedClips.length; ++i )
{
if ( linkedClipItem ) { var linkedClipItem = findClipItem( target.linkedClips[i] );
var newLinkedClipPos = findNewPosition( newPos, linkedClipItem, drag.source, isMagneticMode ); if ( target === drag.source )
linkedClipItem.z = ++maxZ;
// If linked clip collides
if ( newLinkedClipPos !== newPos ) { if ( linkedClipItem ) {
// Recalculate target's newX var newLinkedClipPos = findNewPosition( newPos, linkedClipItem, drag.source, isMagneticMode );
// This time, don't use magnets
if ( isMagneticMode === true ) // If linked clip collides
{ if ( newLinkedClipPos !== newPos ) {
newLinkedClipPos = findNewPosition( newPos, linkedClipItem, drag.source, false ); // Recalculate target's newX
newPos = findNewPosition( newPos, target, drag.source, false ); // This time, don't use magnets
if ( isMagneticMode === true )
// And if newX collides again, we don't move {
if ( newLinkedClipPos !== newPos ) newLinkedClipPos = findNewPosition( newPos, linkedClipItem, drag.source, false );
deltaPos = 0 newPos = findNewPosition( newPos, target, drag.source, false );
// And if newX collides again, we don't move
if ( newLinkedClipPos !== newPos )
deltaPos = 0
else
linkedClipItem.position = target.position; // Link if possible
}
else else
linkedClipItem.position = target.position; // Link if possible deltaPos = 0;
} }
else else
deltaPos = 0; linkedClipItem.position = target.position; // Link if possible
}
else
linkedClipItem.position = target.position; // Link if possible
var ind = toMove.indexOf( linkedClipItem.uuid ); var ind = toMove.indexOf( linkedClipItem.uuid );
if ( ind > 0 ) if ( ind > 0 )
toMove.splice( ind, 1 ); toMove.splice( ind, 1 );
}
} }
}
newPos = oldPos + deltaPos; newPos = oldPos + deltaPos;
toMove.splice( 0, 1 ); toMove.splice( 0, 1 );
} }
// END of while ( toMove.length > 0 ) // END of while ( toMove.length > 0 )
if ( deltaPos === 0 && dMode === dropMode.Move ) { if ( deltaPos === 0 && mode === dropMode.Move ) {
drag.source.forcePosition(); // Use the original position drag.source.forcePosition(); // Use the original position
return; return;
} }
for ( i = 0; i < selectedClips.length; ++i ) { for ( i = 0; i < selectedClips.length; ++i ) {
target = findClipItem( selectedClips[i] ); target = findClipItem( selectedClips[i] );
newPos = target.position + deltaPos; newPos = target.position + deltaPos;
// We only want to update the length when the right edge of the timeline // We only want to update the length when the right edge of the timeline
// is exposed. // is exposed.
if ( sView.flickableItem.contentX + page.width > sView.width && if ( sView.flickableItem.contentX + page.width > sView.width &&
length < newPos + target.length ) { length < newPos + target.length ) {
length = newPos + target.length; length = newPos + target.length;
} }
target.position = newPos; target.position = newPos;
// Scroll if needed // Scroll if needed
if ( drag.source === target || dMode === dropMode.New ) if ( drag.source === target || mode === dropMode.New )
target.scrollToThis(); target.scrollToThis();
}
} }
if ( dMode === dropMode.Move ) if ( mode === dropMode.Move )
lastPos = drag.source.position; lastPos = drag.source.position;
else if ( mode === dropMode.TransitionMove )
lastPos = drag.source.begin;
else else
lastPos = ptof( drag.x ); lastPos = ptof( drag.x );
} }
} }
Repeater { Repeater {
id: repeater id: clipRepeater
model: clips model: clips
delegate: Clip { delegate: Clip {
height: track.height - 3 height: track.height - 3
...@@ -277,6 +331,20 @@ Item { ...@@ -277,6 +331,20 @@ Item {
clipInfo: model clipInfo: model
} }
} }
Repeater {
id: transitionRepeater
model: transitionModel
delegate: TransitionItem {
identifier: model.identifier
uuid: model.uuid
begin: model.begin
end: model.end
trackId: track.trackId
type: track.type
transitionInfo: model
}
}
} }
Rectangle { Rectangle {
......
import QtQuick 2.0 import QtQuick 2.0
ListView { Rectangle {
id: container id: container
width: parent.width width: parent.width
height: tracks.count * trackHeight height: tracks.count * trackHeight
verticalLayoutDirection: isUpward ? ListView.BottomToTop : ListView.TopToBottom color: "#222222"
interactive: false
focus: true
model: tracks
delegate: Track {
trackId: index
type: container.type
clips: model["clips"]
}
property ListModel tracks
property bool isUpward property bool isUpward
property string type property string type
property ListModel tracks
}
ListView {
anchors.fill: parent
verticalLayoutDirection: isUpward ? ListView.BottomToTop : ListView.TopToBottom
interactive: false
focus: true
model: tracks
delegate: Track {
trackId: index
type: container.type
clips: model["clips"]
transitionModel: model["transitions"]
}
}
}
...@@ -20,10 +20,13 @@ Rectangle { ...@@ -20,10 +20,13 @@ Rectangle {
property var allClips: [] // Actual clip item objects property var allClips: [] // Actual clip item objects
property var allClipsDict: ({}) // Actual clip item objects property var allClipsDict: ({}) // Actual clip item objects
property var selectedClips: [] // Selected clip uuids property var selectedClips: [] // Selected clip uuids
property var allTransitions: [] // Actual transition item objects
property var allTransitionsDict: ({}) // Actual transition item objects
property var groups: [] // list of lists of clip uuids property var groups: [] // list of lists of clip uuids
property var linkedClipsDict: ({}) // Uuid property var linkedClipsDict: ({}) // Uuid
property alias isMagneticMode: magneticModeButton.selected property alias isMagneticMode: magneticModeButton.selected
property bool isCutMode: false property bool isCutMode: false
property bool isTransitionMode: transitionModeButton.selected
property bool dragging: false property bool dragging: false
property int trackHeight: 60 property int trackHeight: 60
...@@ -69,23 +72,76 @@ Rectangle { ...@@ -69,23 +72,76 @@ Rectangle {
var cPos = clip.uuid === dragSource.uuid ? ptof( dragSource.x ) : clip["position"]; var cPos = clip.uuid === dragSource.uuid ? ptof( dragSource.x ) : clip["position"];
var cEndPos = clip["position"] + clip["length"] - 1; var cEndPos = clip["position"] + clip["length"] - 1;
if ( cEndPos >= newPos && newPos + target.length - 1 >= cPos ) // Note that in transition mode, they will never collide.
isCollided = true; if ( isTransitionMode === true ) {
leastDistance = ptof( magneticMargin );
// HACK: If magnetic mode, consider clips bigger if ( Math.abs( newPos - cPos ) < leastDistance ) {
// but not if "clip" is also selected because both of them will be moving leastDistance = Math.abs( newPos - cPos );
// and we want to keep the same distance between them as much as possible newPos = cPos;
var clipMargin = useMagneticMode && findClipItem( clip.uuid ).selected === false ? ptof( magneticMargin ) : 0; }
cPos -= clipMargin; if ( Math.abs( newPos + target.length - 1 - cPos ) < leastDistance ) {
cEndPos += clipMargin; leastDistance = Math.abs( newPos + target.length - 1 - cPos );
if ( cEndPos >= newPos && newPos + target.length - 1 >= cPos ) { newPos = cPos - target.length + 1;
if ( cPos >= newPos ) { }
if ( cPos - target.length + 1 > 0 ) if ( Math.abs( newPos - cEndPos ) < leastDistance ) {
newPos = cPos - target.length + clipMargin; leastDistance = Math.abs( newPos - cEndPos );
else newPos = cEndPos;
newPos = target.position; }
} else { if ( Math.abs( newPos + target.length - 1 - cEndPos ) < leastDistance ) {
newPos = cEndPos - clipMargin + 1; leastDistance = Math.abs( newPos + target.length - 1 - cEndPos );
newPos = cEndPos - target.length + 1;
}
// If they overlap, create a cross-dissolve transition
if ( cEndPos >= newPos && newPos + target.length - 1 >= cPos )
{
var toCreate = true;
for ( var i = 0; i < allTransitions.length; ++i ) {
var transitionItem = allTransitions[i];
if ( transitionItem.trackId === clip.trackId &&
transitionItem.type === clip.type &&
transitionItem.isCrossDissolve === true &&
transitionItem.clips.indexOf( clip.uuid ) !== -1 &&
transitionItem.clips.indexOf( target.uuid ) !== -1
) {
transitionItem.begin = Math.max( newPos, clip["position"] );
transitionItem.end = Math.min( newPos + target.length - 1, clip["position"] + clip["length"] - 1 );
toCreate = false;
}
}
if ( toCreate === true ) {
addTransition( target.type, target.newTrackId,
{ "begin": Math.max( newPos, cPos ),
"end": Math.min( newPos + target.length - 1, cEndPos ),
"identifier": "dissolve",
"uuid": "transitionUuid" } );
transitionItem = allTransitions[allTransitions.length - 1];
transitionItem.clips.push( clip.uuid );
transitionItem.clips.push( target.uuid );
}
}
}
else {
// In theory, they share the same deltaPos, therefore unable to collide each other.
if ( findClipItem( clip.uuid ).selected === true )
continue;
if ( cEndPos >= newPos && newPos + target.length - 1 >= cPos )
isCollided = true;
// HACK: If magnetic mode, consider clips bigger
var clipMargin = useMagneticMode ? ptof( magneticMargin ) : 0;
cPos -= clipMargin;
cEndPos += clipMargin;
if ( cEndPos >= newPos && newPos + target.length - 1 >= cPos ) {
if ( cPos >= newPos ) {
if ( cPos - target.length + 1 > 0 )
newPos = cPos - target.length + clipMargin;
else
newPos = target.position;
} else {
newPos = cEndPos - clipMargin + 1;
}
} }
} }
...@@ -159,7 +215,7 @@ Rectangle { ...@@ -159,7 +215,7 @@ Rectangle {
function addTrack( trackType ) function addTrack( trackType )
{ {
trackContainer( trackType )["tracks"].append( { "clips": [] } ); trackContainer( trackType )["tracks"].append( { "clips": [], "transitions": [] } );
} }
function removeTrack( trackType ) function removeTrack( trackType )
...@@ -178,6 +234,7 @@ Rectangle { ...@@ -178,6 +234,7 @@ Rectangle {
newDict["libraryUuid"] = clipDict["libraryUuid"]; newDict["libraryUuid"] = clipDict["libraryUuid"];
newDict["uuid"] = clipDict["uuid"]; newDict["uuid"] = clipDict["uuid"];
newDict["trackId"] = trackId; newDict["trackId"] = trackId;
newDict["type"] = trackType;
newDict["name"] = clipDict["name"]; newDict["name"] = clipDict["name"];
newDict["selected"] = clipDict["selected"] === false ? false : true ; newDict["selected"] = clipDict["selected"] === false ? false : true ;
var tracks = trackContainer( trackType )["tracks"]; var tracks = trackContainer( trackType )["tracks"];
...@@ -247,10 +304,91 @@ Rectangle { ...@@ -247,10 +304,91 @@ Rectangle {
return v; return v;
} }
function addTransition( trackType, trackId, transitionDict )
{
var newDict = {};
newDict["begin"] = transitionDict["begin"];
newDict["end"] = transitionDict["end"];
newDict["uuid"] = transitionDict["uuid"];