Commit a43a5377 authored by luyikei's avatar luyikei

Timeline: Implement transition

parent ec92497e
......@@ -217,6 +217,11 @@ Rectangle {
if ( uuid === "videoUuid" || uuid === "audioUuid" )
return;
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: {
......
......@@ -9,11 +9,11 @@ Item {
property int trackId
property string type
property ListModel clips
property ListModel transitionModel
Rectangle {
Item {
id: clipArea
x: trackInfo.width
color: "#222222"
height: parent.height
width: track.width - initPosOfCursor
......@@ -34,23 +34,26 @@ Item {
DropArea {
id: dropArea
anchors.fill: parent
keys: ["Clip", "vlmc/uuid"]
keys: ["Clip", "Transition", "vlmc/uuid", "vlmc/transition_id"]
// Enum for drop mode
readonly property var dropMode: {
"New": 0,
"Move": 1,
"TransitionNew": 2,
"TransitionMove": 3,
}
property string currentUuid
property var aClipInfo: null
property var vClipInfo: null
property var mode
property int lastPos: 0
property int deltaPos: 0
onDropped: {
if ( drop.keys.indexOf( "vlmc/uuid" ) >= 0 ) {
if ( mode === dropMode.New ) {
aClipInfo = findClipFromTrack( "Audio", trackId, "audioUuid" );
vClipInfo = findClipFromTrack( "Video", trackId, "videoUuid" );
var pos = 0;
......@@ -70,17 +73,38 @@ Item {
adjustTracks( "Audio" );
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: {
if ( currentUuid !== "" ) {
if ( currentUuid === "transitionUuid" ) {
removeTransitionFromTrack( type, trackId, "transitionUuid" );
}
else if ( currentUuid !== "" ) {
removeClipFromTrack( "Audio", trackId, "audioUuid" );
removeClipFromTrack( "Video", trackId, "videoUuid" );
}
}
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();
if ( currentUuid === drag.getDataAsString( "vlmc/uuid" ) ) {
if ( aClipInfo )
......@@ -109,13 +133,31 @@ Item {
}
lastPos = ptof( drag.x );
}
else {
else if ( mode === dropMode.Move ) {
lastPos = ptof( drag.source.x );
// HACK: Call onPositoinChanged forcely here.
// x will be rounded so it won't affect actual its position.
drag.source.x = drag.source.x + 0.000001;
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: {
......@@ -123,26 +165,19 @@ Item {
if ( drag.source.resizing === true )
return;
if ( drag.keys.indexOf( "vlmc/uuid" ) >= 0 )
var dMode = dropMode.New;
else
dMode = dropMode.Move;
// Scroll to the drag source
if ( dMode === dropMode.Move )
if ( mode === dropMode.Move ) {
drag.source.scrollToThis();
if ( dMode === dropMode.Move ) {
deltaPos = ptof( drag.source.x ) - lastPos;
// Move to the top
drag.source.parent.parent.z = ++maxZ;
drag.source.z = ++maxZ;
// Prepare newTrackId for all the selected clips
var oldTrackId = drag.source.newTrackId;
drag.source.newTrackId = trackId;
sortSelectedClips( trackId - oldTrackId ,deltaPos );
deltaPos = ptof( drag.source.x ) - lastPos;
sortSelectedClips( trackId - oldTrackId, deltaPos );
var toMove = selectedClips.concat();
// Check if there is any impossible move
......@@ -179,89 +214,108 @@ Item {
}
}
}
else {
else if ( mode === dropMode.New ){
toMove = selectedClips.concat();
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 ) {
target = findClipItem( toMove[0] );
var oldPos = target.position;
var newPos = findNewPosition( Math.max( oldPos + deltaPos, 0 ), target, drag.source, isMagneticMode );
deltaPos = newPos - oldPos;
// Let's find newX of the linked clip
for ( i = 0; i < target.linkedClips.length; ++i )
{
var linkedClipItem = findClipItem( target.linkedClips[i] );
if ( linkedClipItem ) {
var newLinkedClipPos = findNewPosition( newPos, linkedClipItem, drag.source, isMagneticMode );
// If linked clip collides
if ( newLinkedClipPos !== newPos ) {
// Recalculate target's newX
// This time, don't use magnets
if ( isMagneticMode === true )
{
newLinkedClipPos = findNewPosition( newPos, linkedClipItem, drag.source, false );
newPos = findNewPosition( newPos, target, drag.source, false );
// And if newX collides again, we don't move
if ( newLinkedClipPos !== newPos )
deltaPos = 0
if ( mode === dropMode.New || mode === dropMode.Move )
{
while ( toMove.length > 0 ) {
target = findClipItem( toMove[0] );
var oldPos = target.position;
var newPos = findNewPosition( Math.max( oldPos + deltaPos, 0 ), target, drag.source, isMagneticMode );
deltaPos = newPos - oldPos;
// Let's find newX of the linked clip
for ( i = 0; i < target.linkedClips.length; ++i )
{
var linkedClipItem = findClipItem( target.linkedClips[i] );
if ( target === drag.source )
linkedClipItem.z = ++maxZ;
if ( linkedClipItem ) {
var newLinkedClipPos = findNewPosition( newPos, linkedClipItem, drag.source, isMagneticMode );
// If linked clip collides
if ( newLinkedClipPos !== newPos ) {
// Recalculate target's newX
// This time, don't use magnets
if ( isMagneticMode === true )
{
newLinkedClipPos = findNewPosition( newPos, linkedClipItem, drag.source, false );
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
linkedClipItem.position = target.position; // Link if possible
deltaPos = 0;
}
else
deltaPos = 0;
}
else
linkedClipItem.position = target.position; // Link if possible
linkedClipItem.position = target.position; // Link if possible
var ind = toMove.indexOf( linkedClipItem.uuid );
if ( ind > 0 )
toMove.splice( ind, 1 );
var ind = toMove.indexOf( linkedClipItem.uuid );
if ( ind > 0 )
toMove.splice( ind, 1 );
}
}
}
newPos = oldPos + deltaPos;
toMove.splice( 0, 1 );
}
// END of while ( toMove.length > 0 )
newPos = oldPos + deltaPos;
toMove.splice( 0, 1 );
}
// END of while ( toMove.length > 0 )
if ( deltaPos === 0 && dMode === dropMode.Move ) {
drag.source.forcePosition(); // Use the original position
return;
}
if ( deltaPos === 0 && mode === dropMode.Move ) {
drag.source.forcePosition(); // Use the original position
return;
}
for ( i = 0; i < selectedClips.length; ++i ) {
target = findClipItem( selectedClips[i] );
newPos = target.position + deltaPos;
for ( i = 0; i < selectedClips.length; ++i ) {
target = findClipItem( selectedClips[i] );
newPos = target.position + deltaPos;
// We only want to update the length when the right edge of the timeline
// is exposed.
if ( sView.flickableItem.contentX + page.width > sView.width &&
length < newPos + target.length ) {
length = newPos + target.length;
}
// We only want to update the length when the right edge of the timeline
// is exposed.
if ( sView.flickableItem.contentX + page.width > sView.width &&
length < newPos + target.length ) {
length = newPos + target.length;
}
target.position = newPos;
target.position = newPos;
// Scroll if needed
if ( drag.source === target || dMode === dropMode.New )
target.scrollToThis();
// Scroll if needed
if ( drag.source === target || mode === dropMode.New )
target.scrollToThis();
}
}
if ( dMode === dropMode.Move )
if ( mode === dropMode.Move )
lastPos = drag.source.position;
else if ( mode === dropMode.TransitionMove )
lastPos = drag.source.begin;
else
lastPos = ptof( drag.x );
}
}
Repeater {
id: repeater
id: clipRepeater
model: clips
delegate: Clip {
height: track.height - 3
......@@ -277,6 +331,20 @@ Item {
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 {
......
import QtQuick 2.0
ListView {
Rectangle {
id: container
width: parent.width
height: tracks.count * trackHeight
verticalLayoutDirection: isUpward ? ListView.BottomToTop : ListView.TopToBottom
interactive: false
focus: true
model: tracks
delegate: Track {
trackId: index
type: container.type
clips: model["clips"]
}
color: "#222222"
property ListModel tracks
property bool isUpward
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 {
property var allClips: [] // Actual clip item objects
property var allClipsDict: ({}) // Actual clip item objects
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 linkedClipsDict: ({}) // Uuid
property alias isMagneticMode: magneticModeButton.selected
property bool isCutMode: false
property bool isTransitionMode: transitionModeButton.selected
property bool dragging: false
property int trackHeight: 60
......@@ -69,23 +72,76 @@ Rectangle {
var cPos = clip.uuid === dragSource.uuid ? ptof( dragSource.x ) : clip["position"];
var cEndPos = clip["position"] + clip["length"] - 1;
if ( cEndPos >= newPos && newPos + target.length - 1 >= cPos )
isCollided = true;
// HACK: If magnetic mode, consider clips bigger
// but not if "clip" is also selected because both of them will be moving
// and we want to keep the same distance between them as much as possible
var clipMargin = useMagneticMode && findClipItem( clip.uuid ).selected === false ? 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;
// Note that in transition mode, they will never collide.
if ( isTransitionMode === true ) {
leastDistance = ptof( magneticMargin );
if ( Math.abs( newPos - cPos ) < leastDistance ) {
leastDistance = Math.abs( newPos - cPos );
newPos = cPos;
}
if ( Math.abs( newPos + target.length - 1 - cPos ) < leastDistance ) {
leastDistance = Math.abs( newPos + target.length - 1 - cPos );
newPos = cPos - target.length + 1;
}
if ( Math.abs( newPos - cEndPos ) < leastDistance ) {
leastDistance = Math.abs( newPos - cEndPos );
newPos = cEndPos;
}
if ( Math.abs( newPos + target.length - 1 - cEndPos ) < leastDistance ) {
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 {
function addTrack( trackType )
{
trackContainer( trackType )["tracks"].append( { "clips": [] } );
trackContainer( trackType )["tracks"].append( { "clips": [], "transitions": [] } );
}
function removeTrack( trackType )
......@@ -178,6 +234,7 @@ Rectangle {
newDict["libraryUuid"] = clipDict["libraryUuid"];
newDict["uuid"] = clipDict["uuid"];
newDict["trackId"] = trackId;
newDict["type"] = trackType;
newDict["name"] = clipDict["name"];
newDict["selected"] = clipDict["selected"] === false ? false : true ;
var tracks = trackContainer( trackType )["tracks"];
......@@ -247,10 +304,91 @@ Rectangle {
return v;
}
function addTransition( trackType, trackId, transitionDict )
{
var newDict = {};
newDict["begin"] = transitionDict["begin"];
newDict["end"] = transitionDict["end"];
newDict["uuid"] = transitionDict["uuid"];
newDict["trackId"] = trackId;
newDict["type"] = trackType;
newDict["identifier"] = transitionDict["identifier"];
newDict["name"] = transitionDict["name"];
var tracks = trackContainer( trackType )["tracks"];
while ( trackId > tracks.count - 1 )
addTrack( trackType );
tracks.get( trackId )["transitions"].append( newDict );
return newDict;
}
function removeTransitionFromTrack( trackType, trackId, uuid )
{
var ret = false;
var tracks = trackContainer( trackType )["tracks"];
var trans = tracks.get( trackId )["transitions"];
for ( var j = 0; j < trans.count; j++ ) {
var t = trans.get( j );
if ( t.uuid === uuid ) {
trans.remove( j );
ret = true;
j--;
}
}
return ret;
}
function removeTransitionFromTrackContainer( trackType, uuid )
{
for ( var i = 0; i < trackContainer( trackType )["tracks"].count; ++i )
removeTransitionFromTrack( trackType, i, uuid );
}
function removeTransition( uuid )
{
removeTransitionFromTrackContainer( "Audio", uuid );
removeTransitionFromTrackContainer( "Video", uuid );
}
function findTransitionFromTrack( trackType, trackId, uuid )
{
var trans = trackContainer( trackType )["tracks"].get( trackId )["transitions"];
for ( var j = 0; j < trans.count; ++j ) {
var t = trans.get( j );
if ( t.uuid === uuid )
return t;
}
return null;
}
function findTransitionFromTrackContainer( trackType, uuid )
{
var tracks = trackContainer( trackType )["tracks"];
for ( var i = 0; i < tracks.count; ++i ) {
var t = findTransitionFromTrack( trackType, i, uuid );
if( t )
return t;
}
return null;
}
function findTransition( uuid )
{
var t = findTransitionFromTrackContainer( "Video", uuid );
if ( !t )
return findTransitionFromTrackContainer( "Audio", uuid );
return t;
}
function findClipItem( uuid ) {
return allClipsDict[uuid];
}
function findTransitionItem( uuid ) {
return allTransitionsDict[uuid];
}
function adjustTracks( trackType ) {
var tracks = trackContainer( trackType )["tracks"];
......@@ -410,10 +548,43 @@ Rectangle {
dragging = false;
sortSelectedClips( deltaTrackId, deltaPos );
var toAdd = [];
var toMove = [];
for ( var i = 0; i < allTransitions.length; ++i ) {
var transitionItem = allTransitions[i];
if ( transitionItem.inTrack === true ) {
if ( transitionItem.uuid === "transitionUuid" ) {
toAdd.push( [transitionItem.identifier, transitionItem.begin, transitionItem.end,
transitionItem.trackId, transitionItem.type, transitionItem.clips] );
}
}
}
for ( i = 0; i < allTransitions.length; ++i ) {
transitionItem = allTransitions[i];
if ( transitionItem.inTrack === true ) {
if ( transitionItem.uuid !== "transitionUuid" )
toMove.push( [transitionItem.uuid,
transitionItem.begin,
transitionItem.end] );
}
}
removeTransition( "transitionUuid" );
for ( i = 0; i < toAdd.length; ++i ) {
var newUuid = workflow.addTransition( toAdd[i][0], toAdd[i][1], toAdd[i][2], toAdd[i][3], toAdd[i][4] );
findTransitionItem( newUuid ).clips = toAdd[i][5];
}
for ( i = 0; i < toMove.length; ++i )
workflow.moveTransition( toMove[i][0], toMove[i][1], toMove[i][2] );
// We don't want to rely on selectedClips while moving since it "will" be changed
// I'm aware that it's not the best solution but it's the safest solution for sure
var toMove = [];
for ( var i = 0; i < selectedClips.length; ++i )
toMove = [];
for ( i = 0; i < selectedClips.length; ++i )
{
var clip = findClipItem( selectedClips[i] );
toMove.push( [clip.uuid, clip.newTrackId, clip.position] );
......@@ -606,6 +777,12 @@ Rectangle {
selected: true
}
PropertyButton {
id: transitionModeButton
text: "T"
selected: false
}
PropertyButton {
id: zoomInButton
text: "+"
......@@ -721,8 +898,11 @@ Rectangle {
addClip( type, clipInfo["trackId"], clipInfo );
removeClipFromTrack( type, oldClip["trackId"], uuid );
}
findClipItem( uuid ).position = clipInfo["position"];
findClipItem( uuid ).lastPosition = clipInfo["position"];
else
{
findClipItem( uuid ).position = clipInfo["position"];
findClipItem( uuid ).lastPosition = clipInfo["position"];
}
adjustTracks( type );
}
......@@ -767,6 +947,33 @@ Rectangle {
updateLinkedClips( uuidB );
}
onTransitionAdded: {
var transitionInfo = workflow.transitionInfo( uuid );
var type = transitionInfo["audio"] ? "Audio" : "Video";
if ( transitionInfo["isInTrack"] )
addTransition( type, transitionInfo["trackId"], transitionInfo );
else
addTransition( type, transitionInfo["trackBId"], transitionInfo );
}
onTransitionMoved: {
var transitionInfo = workflow.transitionInfo( uuid );
var transition = findTransition( uuid );
var type = transitionInfo["audio"] ? "Audio" : "Video";
if ( transitionInfo["isInTrack"] || transitionInfo["trackBId"] === transition["trackId"] ) {
transition["begin"] = transitionInfo["begin"];
transition["end"] = transitionInfo["end"];
}
else {
removeTransition( uuid );
addTransition( type, transitionInfo["trackBId"], transitionInfo );
}
}
onTransitionRemoved: {
removeTransition( uuid );
}
onEffectsUpdated: {
var item = findClipItem( clipUuid );
if ( item )
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment