Initial implementation of clip speed change on Ctrl + resize

parent 738b8e13
Pipeline #4319 passed with stage
in 18 minutes and 39 seconds
......@@ -152,11 +152,13 @@ bool ClipModel::requestResize(int size, bool right, Fun &undo, Fun &redo, bool l
int out = m_producer->get_out();
int old_in = in, old_out = out;
// check if there is enough space on the chosen side
if (!right && in + delta < 0 && !m_endlessResize) {
return false;
}
if (!m_endlessResize && right && (out - delta >= m_producer->get_length())) {
return false;
if (!m_endlessResize) {
if (!right && in + delta < 0) {
return false;
}
if (right && (out - delta >= m_producer->get_length())) {
return false;
}
}
if (right) {
out -= delta;
......@@ -212,28 +214,30 @@ bool ClipModel::requestResize(int size, bool right, Fun &undo, Fun &redo, bool l
return false;
};
if (operation()) {
// Now, we are in the state in which the timeline should be when we try to revert current action. So we can build the reverse action from here
if (m_currentTrackId != -1) {
if (auto ptr = m_parent.lock()) {
track_reverse = ptr->getTrackById(m_currentTrackId)->requestClipResize_lambda(m_id, old_in, old_out, right);
Fun reverse = []() { return true; };
if (logUndo) {
// Now, we are in the state in which the timeline should be when we try to revert current action. So we can build the reverse action from here
if (m_currentTrackId != -1) {
if (auto ptr = m_parent.lock()) {
track_reverse = ptr->getTrackById(m_currentTrackId)->requestClipResize_lambda(m_id, old_in, old_out, right);
}
}
}
Fun reverse = [this, old_in, old_out, track_reverse, roles]() {
if (track_reverse()) {
setInOut(old_in, old_out);
if (m_currentTrackId > -1) {
if (auto ptr = m_parent.lock()) {
QModelIndex ix = ptr->makeClipIndexFromID(m_id);
ptr->dataChanged(ix, ix, roles);
reverse = [this, old_in, old_out, track_reverse, roles]() {
if (track_reverse()) {
setInOut(old_in, old_out);
if (m_currentTrackId > -1) {
if (auto ptr = m_parent.lock()) {
QModelIndex ix = ptr->makeClipIndexFromID(m_id);
ptr->dataChanged(ix, ix, roles);
}
}
return true;
}
return true;
}
return false;
};
qDebug() << "----------\n-----------\n// ADJUSTING EFFECT LENGTH, LOGUNDO " << logUndo << ", " << old_in << "/" << inPoint << ", "
return false;
};
qDebug() << "----------\n-----------\n// ADJUSTING EFFECT LENGTH, LOGUNDO " << logUndo << ", " << old_in << "/" << inPoint << ", "
<< m_producer->get_playtime();
if (logUndo) {
adjustEffectLength(right, old_in, inPoint, old_out - old_in, m_producer->get_playtime(), offset, reverse, operation, logUndo);
}
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
......@@ -411,7 +415,7 @@ void ClipModel::refreshProducerFromBin()
refreshProducerFromBin(m_currentState);
}
bool ClipModel::useTimewarpProducer(double speed, Fun &undo, Fun &redo)
bool ClipModel::useTimewarpProducer(double speed, bool changeDuration, Fun &undo, Fun &redo)
{
if (m_endlessResize) {
// no timewarp for endless producers
......@@ -430,7 +434,7 @@ bool ClipModel::useTimewarpProducer(double speed, Fun &undo, Fun &redo)
int oldIn = getIn();
auto operation = useTimewarpProducer_lambda(speed);
auto reverse = useTimewarpProducer_lambda(previousSpeed);
if (oldOut >= newDuration) {
if (changeDuration && oldOut >= newDuration) {
// in that case, we are going to shrink the clip when changing the producer. We must undo that when reloading the old producer
reverse = [reverse, oldIn, oldOut, this]() {
bool res = reverse();
......@@ -443,10 +447,12 @@ bool ClipModel::useTimewarpProducer(double speed, Fun &undo, Fun &redo)
if (operation()) {
UPDATE_UNDO_REDO(operation, reverse, local_undo, local_redo);
// When calculating duration, result can be a few frames longer than possible duration so adjust
bool res = requestResize(qMin(newDuration, getMaxDuration()), true, local_undo, local_redo, true);
if (!res) {
local_undo();
return false;
if (changeDuration) {
bool res = requestResize(qMin(newDuration, getMaxDuration()), true, local_undo, local_redo, true);
if (!res) {
local_undo();
return false;
}
}
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
......
......@@ -183,7 +183,7 @@ protected:
/* @brief This functions replaces the current producer with a slowmotion one
It also resizes the producer so that set of frames contained in the clip is the same
*/
bool useTimewarpProducer(double speed, Fun &undo, Fun &redo);
bool useTimewarpProducer(double speed, bool changeDuration, Fun &undo, Fun &redo);
// @brief Lambda that merely changes the speed (in and out are untouched)
Fun useTimewarpProducer_lambda(double speed);
......
......@@ -96,7 +96,7 @@ RTTR_REGISTRATION
// .method("requestCompositionInsertion", select_overload<bool(const QString &, int, int, int, std::unique_ptr<Mlt::Properties>, int &, bool)>(
// &TimelineModel::requestCompositionInsertion))(
// parameter_names("transitionId", "trackId", "position", "length", "transProps", "id", "logUndo"))
.method("requestClipTimeWarp", select_overload<bool(int, double)>(&TimelineModel::requestClipTimeWarp))(parameter_names("clipId", "speed"));
.method("requestClipTimeWarp", select_overload<bool(int, double,bool)>(&TimelineModel::requestClipTimeWarp))(parameter_names("clipId", "speed","changeDuration"));
}
int TimelineModel::next_id = 0;
......@@ -1665,20 +1665,83 @@ const std::vector<int> TimelineModel::getBoundaries(int itemId)
return boundaries;
}
int TimelineModel::requestItemResize(int itemId, int size, bool right, bool logUndo, int snapDistance, bool allowSingleResize)
int TimelineModel::requestClipResizeAndTimeWarp(int itemId, int size, bool right, int snapDistance, bool allowSingleResize, double speed)
{
if (logUndo) {
qDebug() << "---------------------\n---------------------\nRESIZE W/UNDO CALLED\n++++++++++++++++\n++++";
}
QWriteLocker locker(&m_lock);
TRACE(itemId, size, right, logUndo, snapDistance, allowSingleResize);
Q_ASSERT(isItem(itemId));
TRACE(itemId, size, right, true, snapDistance, allowSingleResize);
Q_ASSERT(isClip(itemId));
if (size <= 0) {
TRACE_RES(-1);
return -1;
}
int in = getItemPosition(itemId);
int out = in + getItemPlaytime(itemId);
//size = requestItemResizeInfo(itemId, in, out, size, right, snapDistance);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
std::unordered_set<int> all_items;
if (!allowSingleResize && m_groups->isInGroup(itemId)) {
int groupId = m_groups->getRootId(itemId);
std::unordered_set<int> items;
if (m_groups->getType(groupId) == GroupType::AVSplit) {
// Only resize group elements if it is an avsplit
items = m_groups->getLeaves(groupId);
} else {
all_items.insert(itemId);
}
for (int id : items) {
if (id == itemId) {
all_items.insert(id);
continue;
}
int start = getItemPosition(id);
int end = in + getItemPlaytime(id);
if (right) {
if (out == end) {
all_items.insert(id);
}
} else if (start == in) {
all_items.insert(id);
}
}
} else {
all_items.insert(itemId);
}
bool result = true;
for (int id : all_items) {
int tid = getItemTrackId(id);
if (tid > -1 && getTrackById_const(tid)->isLocked()) {
continue;
}
// First delete clip, then timewarp, resize and reinsert
int pos = getItemPosition(id);
if (!right) {
pos += getItemPlaytime(id) - size;
}
result = getTrackById(tid)->requestClipDeletion(id, true, true, undo, redo, false, true);
result = result && requestClipTimeWarp(id, speed, false, undo, redo);
result = result && requestItemResize(id, size, true, true, undo, redo);
result = result && getTrackById(tid)->requestClipInsertion(id, pos, true, true, undo, redo);
if (!result) {
break;
}
}
if (!result) {
bool undone = undo();
Q_ASSERT(undone);
TRACE_RES(-1);
return -1;
}
if (result) {
PUSH_UNDO(undo, redo, i18n("Resize clip speed"));
}
int res = result ? size : -1;
TRACE_RES(res);
return res;
}
int TimelineModel::requestItemResizeInfo(int itemId, int in, int out, int size, bool right, int snapDistance)
{
if (snapDistance > 0 && getItemTrackId(itemId) != -1) {
Fun temp_undo = []() { return true; };
Fun temp_redo = []() { return true; };
......@@ -1713,6 +1776,24 @@ int TimelineModel::requestItemResize(int itemId, int size, bool right, bool logU
}
}
}
return size;
}
int TimelineModel::requestItemResize(int itemId, int size, bool right, bool logUndo, int snapDistance, bool allowSingleResize)
{
if (logUndo) {
qDebug() << "---------------------\n---------------------\nRESIZE W/UNDO CALLED\n++++++++++++++++\n++++";
}
QWriteLocker locker(&m_lock);
TRACE(itemId, size, right, logUndo, snapDistance, allowSingleResize);
Q_ASSERT(isItem(itemId));
if (size <= 0) {
TRACE_RES(-1);
return -1;
}
int in = getItemPosition(itemId);
int out = in + getItemPlaytime(itemId);
size = requestItemResizeInfo(itemId, in, out, size, right, snapDistance);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
std::unordered_set<int> all_items;
......@@ -3013,7 +3094,7 @@ void TimelineModel::requestClipUpdate(int clipId, const QVector<int> &roles)
notifyChange(modelIndex, modelIndex, roles);
}
bool TimelineModel::requestClipTimeWarp(int clipId, double speed, Fun &undo, Fun &redo)
bool TimelineModel::requestClipTimeWarp(int clipId, double speed, bool changeDuration, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
if (qFuzzyCompare(speed, m_allClips[clipId]->getSpeed())) {
......@@ -3029,7 +3110,7 @@ bool TimelineModel::requestClipTimeWarp(int clipId, double speed, Fun &undo, Fun
success = success && getTrackById(trackId)->requestClipDeletion(clipId, true, true, local_undo, local_redo, false, false);
}
if (success) {
success = m_allClips[clipId]->useTimewarpProducer(speed, local_undo, local_redo);
success = m_allClips[clipId]->useTimewarpProducer(speed, changeDuration, local_undo, local_redo);
}
if (trackId != -1) {
success = success && getTrackById(trackId)->requestClipInsertion(clipId, oldPos, true, true, local_undo, local_redo);
......@@ -3042,7 +3123,7 @@ bool TimelineModel::requestClipTimeWarp(int clipId, double speed, Fun &undo, Fun
return success;
}
bool TimelineModel::requestClipTimeWarp(int clipId, double speed)
bool TimelineModel::requestClipTimeWarp(int clipId, double speed, bool changeDuration)
{
QWriteLocker locker(&m_lock);
TRACE(clipId, speed);
......@@ -3055,10 +3136,10 @@ bool TimelineModel::requestClipTimeWarp(int clipId, double speed)
// Check if clip has a split partner
int splitId = m_groups->getSplitPartner(clipId);
if (splitId > -1) {
result = requestClipTimeWarp(splitId, speed / 100.0, undo, redo);
result = requestClipTimeWarp(splitId, speed / 100.0, changeDuration, undo, redo);
}
if (result) {
result = requestClipTimeWarp(clipId, speed / 100.0, undo, redo);
result = requestClipTimeWarp(clipId, speed / 100.0, changeDuration, undo, redo);
}
if (!result) {
pCore->displayMessage(i18n("Change speed failed"), ErrorMessage);
......@@ -3068,7 +3149,7 @@ bool TimelineModel::requestClipTimeWarp(int clipId, double speed)
}
} else {
// If clip is not inserted on a track, we just change the producer
result = m_allClips[clipId]->useTimewarpProducer(speed, undo, redo);
result = m_allClips[clipId]->useTimewarpProducer(speed, changeDuration, undo, redo);
}
if (result) {
PUSH_UNDO(undo, redo, i18n("Change clip speed"));
......
......@@ -466,6 +466,8 @@ public:
Q_INVOKABLE const QVariantList getGroupData(int itemId);
Q_INVOKABLE void processGroupResize(QVariantList startPos, QVariantList endPos, bool right);
Q_INVOKABLE int requestClipResizeAndTimeWarp(int itemId, int size, bool right, int snapDistance, bool allowSingleResize, double speed);
/* @brief Group together a set of ids
The ids are either a group ids or clip ids. The involved clip must already be inserted in a track
This action is undoable
......@@ -536,6 +538,11 @@ protected:
@returns best snap position or -1 if no snap point is near
*/
int getBestSnapPos(int pos, int length, const std::vector<int> &pts = std::vector<int>(), int cursorPosition = 0, int snapDistance = -1);
/* @brief Returns the best possible size for a clip on resize
*/
int requestItemResizeInfo(int itemId, int in, int out, int size, bool right, int snapDistance);
/* @brief Returns a list of in/out of all items in the group of itemId
*/
const std::vector<int> getBoundaries(int itemId);
......@@ -621,10 +628,10 @@ public:
This functions create an undo object and also apply the effect to the corresponding audio if there is any.
Returns true on success, false otherwise (and nothing is modified)
*/
bool requestClipTimeWarp(int clipId, double speed);
Q_INVOKABLE bool requestClipTimeWarp(int clipId, double speed, bool changeDuration);
/* @brief Same function as above, but doesn't check for paired audio and accumulate undo/redo
*/
bool requestClipTimeWarp(int clipId, double speed, Fun &undo, Fun &redo);
bool requestClipTimeWarp(int clipId, double speed, bool changeDuration, Fun &undo, Fun &redo);
void replugClip(int clipId);
......
......@@ -75,11 +75,11 @@ Rectangle {
width : clipDuration * timeScale;
opacity: dragProxyArea.drag.active && dragProxy.draggedItem == clipId ? 0.8 : 1.0
signal trimmingIn(var clip, real newDuration, var mouse, bool shiftTrim)
signal trimmedIn(var clip, bool shiftTrim)
signal trimmingIn(var clip, real newDuration, var mouse, bool shiftTrim, bool controlTrim)
signal trimmedIn(var clip, bool shiftTrim, bool controlTrim)
signal initGroupTrim(var clip)
signal trimmingOut(var clip, real newDuration, var mouse, bool shiftTrim)
signal trimmedOut(var clip, bool shiftTrim)
signal trimmingOut(var clip, real newDuration, var mouse, bool shiftTrim, bool controlTrim)
signal trimmedOut(var clip, bool shiftTrim, bool controlTrim)
onIsGrabbedChanged: {
if (clipRoot.isGrabbed) {
......@@ -815,6 +815,7 @@ Rectangle {
drag.axis: Drag.XAxis
drag.smoothed: false
property bool shiftTrim: false
property bool controlTrim: false
property bool sizeChanged: false
cursorShape: (containsMouse ? Qt.SizeHorCursor : Qt.ClosedHandCursor);
onPressed: {
......@@ -823,6 +824,7 @@ Rectangle {
clipRoot.originalDuration = clipDuration
parent.anchors.left = undefined
shiftTrim = mouse.modifiers & Qt.ShiftModifier
controlTrim = mouse.modifiers & Qt.ControlModifier
if (!shiftTrim && clipRoot.grouped) {
clipRoot.initGroupTrim(clipRoot)
}
......@@ -832,7 +834,7 @@ Rectangle {
root.stopScrolling = false
parent.anchors.left = clipRoot.left
if (sizeChanged) {
clipRoot.trimmedIn(clipRoot, shiftTrim)
clipRoot.trimmedIn(clipRoot, shiftTrim, controlTrim)
sizeChanged = false
}
}
......@@ -845,7 +847,7 @@ Rectangle {
}
var newDuration = clipDuration - delta
sizeChanged = true
clipRoot.trimmingIn(clipRoot, newDuration, mouse, shiftTrim)
clipRoot.trimmingIn(clipRoot, newDuration, mouse, shiftTrim, controlTrim)
}
}
}
......@@ -877,6 +879,7 @@ Rectangle {
anchors.fill: parent
hoverEnabled: true
property bool shiftTrim: false
property bool controlTrim: false
property bool sizeChanged: false
cursorShape: (containsMouse ? Qt.SizeHorCursor : Qt.ClosedHandCursor);
drag.target: parent
......@@ -888,6 +891,7 @@ Rectangle {
clipRoot.originalDuration = clipDuration
parent.anchors.right = undefined
shiftTrim = mouse.modifiers & Qt.ShiftModifier
controlTrim = mouse.modifiers & Qt.ControlModifier
if (!shiftTrim && clipRoot.grouped) {
clipRoot.initGroupTrim(clipRoot)
}
......@@ -897,7 +901,7 @@ Rectangle {
root.stopScrolling = false
parent.anchors.right = clipRoot.right
if (sizeChanged) {
clipRoot.trimmedOut(clipRoot, shiftTrim)
clipRoot.trimmedOut(clipRoot, shiftTrim, controlTrim)
sizeChanged = false
}
}
......@@ -906,7 +910,7 @@ Rectangle {
var newDuration = Math.round((parent.x + parent.width) / timeScale)
if (newDuration != clipDuration) {
sizeChanged = true
clipRoot.trimmingOut(clipRoot, newDuration, mouse, shiftTrim)
clipRoot.trimmingOut(clipRoot, newDuration, mouse, shiftTrim, controlTrim)
}
}
}
......
......@@ -270,6 +270,15 @@ Column{
clip.groupTrimData = controller.getGroupData(clip.clipId)
}
onTrimmingIn: {
if (controlTrim) {
newDuration = Math.max(1, newDuration)
speedController.x = clip.x + clip.width - newDuration * trackRoot.timeScale
speedController.width = newDuration * trackRoot.timeScale
speedController.lastValidDuration = newDuration
speedController.speedText = (100 * clip.originalDuration * clip.speed / speedController.lastValidDuration).toFixed(2) + '%'
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
......@@ -285,10 +294,18 @@ Column{
}
onTrimmedIn: {
bubbleHelp.hide()
if (shiftTrim || clip.groupTrimData == undefined) {
if (controlTrim) {
speedController.visible = false
}
if (shiftTrim || clip.groupTrimData == undefined || controlTrim) {
// We only resize one element
controller.requestItemResize(clip.clipId, clip.originalDuration, false, false, 0, shiftTrim)
controller.requestItemResize(clip.clipId, clip.lastValidDuration, false, true, 0, shiftTrim)
if (controlTrim) {
// Update speed
controller.requestClipResizeAndTimeWarp(clip.clipId, speedController.lastValidDuration, false, root.snapping, shiftTrim, clip.originalDuration * clip.speed / speedController.lastValidDuration)
} else {
controller.requestItemResize(clip.clipId, clip.lastValidDuration, false, true, 0, shiftTrim)
}
} else {
var updatedGroupData = controller.getGroupData(clip.clipId)
controller.processGroupResize(clip.groupTrimData, updatedGroupData, false)
......@@ -296,6 +313,15 @@ Column{
clip.groupTrimData = undefined
}
onTrimmingOut: {
if (controlTrim) {
speedController.x = clip.x
newDuration = Math.max(1, newDuration)
speedController.width = newDuration * trackRoot.timeScale
speedController.lastValidDuration = newDuration
speedController.speedText = (100 * clip.originalDuration * clip.speed / speedController.lastValidDuration).toFixed(2) + '%'
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
......@@ -310,9 +336,17 @@ Column{
}
onTrimmedOut: {
bubbleHelp.hide()
if (shiftTrim || clip.groupTrimData == undefined) {
if (controlTrim) {
speedController.visible = false
}
if (shiftTrim || clip.groupTrimData == undefined || controlTrim) {
controller.requestItemResize(clip.clipId, clip.originalDuration, true, false, 0, shiftTrim)
controller.requestItemResize(clip.clipId, clip.lastValidDuration, true, true, 0, shiftTrim)
if (controlTrim) {
// Update speed
controller.requestClipResizeAndTimeWarp(clip.clipId, speedController.lastValidDuration, true, root.snapping, shiftTrim, clip.originalDuration * clip.speed / speedController.lastValidDuration)
} else {
controller.requestItemResize(clip.clipId, clip.lastValidDuration, true, true, 0, shiftTrim)
}
} else {
var updatedGroupData = controller.getGroupData(clip.clipId)
controller.processGroupResize(clip.groupTrimData, updatedGroupData, true)
......@@ -366,4 +400,23 @@ Column{
}
}
}
Rectangle {
id: speedController
color: '#aaff0000'
visible: false
height: root.baseUnit * 3
property int lastValidDuration: 0
property string speedText: '100%'
Text {
id: speedLabel
text: i18n("Adjusting speed:\n") + speedController.speedText
font.pixelSize: root.baseUnit * 1.2
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: 'white'
style: Text.Outline
styleColor: 'black'
}
}
}
......@@ -1515,7 +1515,7 @@ void TimelineController::changeItemSpeed(int clipId, double speed)
speed = d->getValue();
qDebug() << "requesting speed " << speed;
}
m_model->requestClipTimeWarp(clipId, speed);
m_model->requestClipTimeWarp(clipId, speed, true);
}
void TimelineController::switchCompositing(int mode)
......
......@@ -59,7 +59,7 @@ TEST_CASE("Test of timewarping", "[Timewarp]")
std::function<bool(void)> undo = []() { return true; };
std::function<bool(void)> redo = []() { return true; };
REQUIRE(timeline->requestClipTimeWarp(cid3, 0.1, undo, redo));
REQUIRE(timeline->requestClipTimeWarp(cid3, 0.1, true, undo, redo));
CHECK_UPDATE(TimelineModel::SpeedRole);
REQUIRE(timeline->getClipSpeed(cid3) == 0.1);
......@@ -81,7 +81,7 @@ TEST_CASE("Test of timewarping", "[Timewarp]")
std::function<bool(void)> undo2 = []() { return true; };
std::function<bool(void)> redo2 = []() { return true; };
REQUIRE(timeline->requestClipTimeWarp(cid3, 1.2, undo2, redo2));
REQUIRE(timeline->requestClipTimeWarp(cid3, 1.2, true, undo2, redo2));
CHECK_UPDATE(TimelineModel::SpeedRole);
REQUIRE(timeline->getClipSpeed(cid3) == 1.2);
......@@ -102,7 +102,7 @@ TEST_CASE("Test of timewarping", "[Timewarp]")
int curLength = timeline->getClipPlaytime(cid3);
// This is the limit, should work
REQUIRE(timeline->requestClipTimeWarp(cid3, double(curLength), undo2, redo2));
REQUIRE(timeline->requestClipTimeWarp(cid3, double(curLength), true, undo2, redo2));
CHECK_UPDATE(TimelineModel::SpeedRole);
REQUIRE(timeline->getClipSpeed(cid3) == double(curLength));
......@@ -110,7 +110,7 @@ TEST_CASE("Test of timewarping", "[Timewarp]")
// This is the higher than the limit, should not work
// (we have some error margin in duration rounding, multiply by 10)
REQUIRE_FALSE(timeline->requestClipTimeWarp(cid3, double(curLength) * 10, undo2, redo2));
REQUIRE_FALSE(timeline->requestClipTimeWarp(cid3, double(curLength) * 10, true, undo2, redo2));
}
binModel->clean();
pCore->m_projectManager = nullptr;
......
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